From e3aa6d6a9705ce07d12ad6f285bb88f25746c97b Mon Sep 17 00:00:00 2001 From: Steve M Date: Sun, 21 Apr 2024 09:32:51 -0700 Subject: [PATCH] Squash development commits for PR --- code/AssetLib/USD/USDLoader.cpp | 120 + code/AssetLib/USD/USDLoader.h | 78 + code/AssetLib/USD/USDLoaderImplTinyusdz.cpp | 643 + code/AssetLib/USD/USDLoaderImplTinyusdz.h | 127 + code/AssetLib/USD/USDLoaderUtil.cpp | 116 + code/AssetLib/USD/USDLoaderUtil.h | 59 + code/CMakeLists.txt | 90 + code/Common/ImporterRegistry.cpp | 6 + contrib/tinyusdz/README.md | 40 + contrib/tinyusdz/assimp_tinyusdz_logging.inc | 54 + contrib/tinyusdz/tinyusdz_repo/LICENSE | 16 + contrib/tinyusdz/tinyusdz_repo/README.md | 529 + .../src/ascii-parser-basetype.cc | 3304 +++++ .../src/ascii-parser-timesamples-array.cc | 338 + .../src/ascii-parser-timesamples.cc | 285 + .../tinyusdz_repo/src/ascii-parser.cc | 4970 ++++++++ .../tinyusdz_repo/src/ascii-parser.hh | 893 ++ .../tinyusdz_repo/src/asset-resolution.cc | 202 + .../tinyusdz_repo/src/asset-resolution.hh | 364 + .../tinyusdz_repo/src/common-macros.inc | 119 + .../tinyusdz/tinyusdz_repo/src/composition.cc | 1734 +++ .../tinyusdz/tinyusdz_repo/src/composition.hh | 305 + .../tinyusdz_repo/src/crate-format.cc | 352 + .../tinyusdz_repo/src/crate-format.hh | 530 + .../tinyusdz_repo/src/crate-pprint.cc | 16 + .../tinyusdz_repo/src/crate-pprint.hh | 18 + .../tinyusdz_repo/src/crate-reader.cc | 6281 ++++++++++ .../tinyusdz_repo/src/crate-reader.hh | 429 + .../tinyusdz_repo/src/define-type-trait.inc | 40 + .../src/external/dtoa_milo.LICENSE | 19 + .../tinyusdz_repo/src/external/dtoa_milo.h | 419 + .../src/external/fast_float/LICENSE-APACHE | 190 + .../src/external/fast_float/LICENSE-BOOST | 23 + .../src/external/fast_float/LICENSE-MIT | 27 + .../src/external/fast_float/README.md | 291 + .../include/fast_float/ascii_number.h | 417 + .../fast_float/include/fast_float/bigint.h | 617 + .../fast_float/constexpr_feature_detect.h | 40 + .../include/fast_float/decimal_to_binary.h | 189 + .../include/fast_float/digit_comparison.h | 426 + .../include/fast_float/fast_float.h | 42 + .../include/fast_float/fast_table.h | 700 ++ .../include/fast_float/float_common.h | 670 + .../include/fast_float/parse_number.h | 231 + .../fast_float/simple_decimal_conversion.h | 360 + .../src/external/filesystem/LICENSE | 19 + .../src/external/filesystem/README.md | 1074 ++ .../filesystem/include/ghc/filesystem.hpp | 6065 +++++++++ .../filesystem/include/ghc/fs_fwd.hpp | 38 + .../filesystem/include/ghc/fs_impl.hpp | 35 + .../filesystem/include/ghc/fs_std.hpp | 60 + .../filesystem/include/ghc/fs_std_fwd.hpp | 63 + .../filesystem/include/ghc/fs_std_impl.hpp | 46 + .../src/external/floaxie/LICENSE | 202 + .../src/external/floaxie/README.md | 112 + .../src/external/floaxie/floaxie/atof.h | 179 + .../src/external/floaxie/floaxie/bit_ops.h | 220 + .../external/floaxie/floaxie/cached_power.h | 53 + .../floaxie/floaxie/conversion_status.h | 34 + .../floaxie/floaxie/default_fallback.h | 94 + .../src/external/floaxie/floaxie/diy_fp.h | 518 + .../src/external/floaxie/floaxie/fraction.h | 142 + .../src/external/floaxie/floaxie/ftoa.h | 202 + .../src/external/floaxie/floaxie/grisu.h | 303 + .../src/external/floaxie/floaxie/huge_val.h | 57 + .../floaxie/floaxie/integer_of_size.h | 51 + .../src/external/floaxie/floaxie/k_comp.h | 54 + .../src/external/floaxie/floaxie/krosh.h | 621 + .../src/external/floaxie/floaxie/memwrap.h | 61 + .../src/external/floaxie/floaxie/powers_ten.h | 47 + .../floaxie/floaxie/powers_ten_double.h | 419 + .../floaxie/floaxie/powers_ten_single.h | 276 + .../src/external/floaxie/floaxie/prettify.h | 221 + .../src/external/floaxie/floaxie/print.h | 63 + .../src/external/floaxie/floaxie/static_pow.h | 164 + .../floaxie/floaxie/type_punning_cast.h | 45 + .../tinyusdz_repo/src/external/fpng.cpp | 3226 +++++ .../tinyusdz_repo/src/external/fpng.h | 122 + .../tinyusdz_repo/src/external/glob/LICENSE | 21 + .../tinyusdz_repo/src/external/glob/README.md | 283 + .../src/external/glob/include/glob/glob.h | 40 + .../glob/single_include/glob/glob.hpp | 448 + .../src/external/glob/source/glob.cpp | 381 + .../tinyusdz_repo/src/external/half-edge.hh | 384 + .../src/external/jsteemann/LICENSE | 202 + .../src/external/jsteemann/README.md | 196 + .../src/external/jsteemann/atoi.h | 250 + .../src/external/jsteemann/modification.md | 3 + .../tinyusdz_repo/src/external/linalg.README | 482 + .../src/external/linalg.UNLICENSE | 24 + .../tinyusdz_repo/src/external/linalg.h | 721 ++ .../src/external/mapbox/earcut/CHANGELOG.md | 27 + .../src/external/mapbox/earcut/LICENSE | 15 + .../src/external/mapbox/earcut/README.md | 131 + .../src/external/mapbox/earcut/earcut.hpp | 816 ++ .../src/external/mapbox/eternal/LICENSE.md | 15 + .../src/external/mapbox/eternal/README.md | 80 + .../mapbox/eternal/include/mapbox/eternal.hpp | 410 + .../src/external/nanoflann.COPYING | 29 + .../tinyusdz_repo/src/external/nanoflann.hpp | 2737 ++++ .../tinyusdz_repo/src/external/stb_image.h | 8007 ++++++++++++ .../src/external/stb_image_resize.h | 2634 ++++ .../src/external/stb_image_resize2.h | 10365 ++++++++++++++++ .../src/external/stb_image_write.h | 1724 +++ .../tinyusdz_repo/src/handle-allocator.hh | 112 + .../tinyusdz_repo/src/image-loader.cc | 466 + .../tinyusdz_repo/src/image-loader.hh | 103 + .../tinyusdz/tinyusdz_repo/src/image-types.hh | 48 + .../tinyusdz/tinyusdz_repo/src/image-util.cc | 794 ++ .../tinyusdz/tinyusdz_repo/src/image-util.hh | 248 + .../tinyusdz_repo/src/image-writer.cc | 131 + .../tinyusdz_repo/src/image-writer.hh | 52 + .../tinyusdz_repo/src/integerCoding.cpp | 516 + .../tinyusdz_repo/src/integerCoding.h | 144 + contrib/tinyusdz/tinyusdz_repo/src/io-util.cc | 760 ++ contrib/tinyusdz/tinyusdz_repo/src/io-util.hh | 177 + .../tinyusdz_repo/src/linear-algebra.cc | 271 + .../tinyusdz_repo/src/linear-algebra.hh | 84 + .../tinyusdz_repo/src/lz4-compression.cc | 259 + .../tinyusdz_repo/src/lz4-compression.hh | 42 + .../tinyusdz/tinyusdz_repo/src/lz4/LICENSE | 24 + contrib/tinyusdz/tinyusdz_repo/src/lz4/lz4.c | 2734 ++++ contrib/tinyusdz/tinyusdz_repo/src/lz4/lz4.h | 842 ++ .../tinyusdz/tinyusdz_repo/src/math-util.inc | 404 + .../tinyusdz_repo/src/nonstd/expected.hpp | 3494 ++++++ .../tinyusdz_repo/src/nonstd/optional.hpp | 1791 +++ .../tinyusdz/tinyusdz_repo/src/path-util.cc | 246 + .../tinyusdz/tinyusdz_repo/src/path-util.hh | 78 + .../tinyusdz/tinyusdz_repo/src/pprinter.cc | 4541 +++++++ .../tinyusdz/tinyusdz_repo/src/pprinter.hh | 284 + .../tinyusdz_repo/src/prim-composition.cc | 25 + .../tinyusdz/tinyusdz_repo/src/prim-pprint.hh | 35 + .../tinyusdz_repo/src/prim-reconstruct.cc | 4781 +++++++ .../tinyusdz_repo/src/prim-reconstruct.hh | 62 + .../tinyusdz_repo/src/prim-type-macros.inc | 31 + .../tinyusdz/tinyusdz_repo/src/prim-types.cc | 2249 ++++ .../tinyusdz/tinyusdz_repo/src/prim-types.hh | 4353 +++++++ contrib/tinyusdz/tinyusdz_repo/src/primvar.cc | 104 + contrib/tinyusdz/tinyusdz_repo/src/primvar.hh | 268 + contrib/tinyusdz/tinyusdz_repo/src/stage.cc | 786 ++ contrib/tinyusdz/tinyusdz_repo/src/stage.hh | 317 + .../tinyusdz/tinyusdz_repo/src/str-util.cc | 678 + .../tinyusdz/tinyusdz_repo/src/str-util.hh | 428 + .../tinyusdz_repo/src/stream-reader.hh | 376 + .../tinyusdz_repo/src/stream-writer.hh | 335 + contrib/tinyusdz/tinyusdz_repo/src/subdiv.cc | 321 + contrib/tinyusdz/tinyusdz_repo/src/subdiv.hh | 109 + .../tinyusdz_repo/src/texture-types.hh | 68 + .../tinyusdz/tinyusdz_repo/src/tiny-any.inc | 633 + .../tinyusdz/tinyusdz_repo/src/tiny-format.cc | 109 + .../tinyusdz/tinyusdz_repo/src/tiny-format.hh | 100 + .../tinyusdz_repo/src/tiny-variant.hh | 356 + .../tinyusdz/tinyusdz_repo/src/tinyusdz.cc | 1424 +++ .../tinyusdz/tinyusdz_repo/src/tinyusdz.hh | 463 + .../tinyusdz/tinyusdz_repo/src/token-type.hh | 206 + .../tinyusdz_repo/src/tydra/README.md | 50 + ...ttribute-eval-typed-animatable-fallback.cc | 267 + .../tydra/attribute-eval-typed-animatable.cc | 272 + .../tydra/attribute-eval-typed-fallback.cc | 247 + .../src/tydra/attribute-eval-typed.cc | 261 + .../tinyusdz_repo/src/tydra/attribute-eval.cc | 236 + .../tinyusdz_repo/src/tydra/attribute-eval.hh | 258 + .../tinyusdz_repo/src/tydra/facial.cc | 97 + .../tinyusdz_repo/src/tydra/facial.hh | 76 + .../tinyusdz_repo/src/tydra/nurbs-tess.hh | 21 + .../tinyusdz_repo/src/tydra/obj-export.cc | 247 + .../tinyusdz_repo/src/tydra/obj-export.hh | 35 + .../tinyusdz_repo/src/tydra/prim-apply.cc | 157 + .../tinyusdz_repo/src/tydra/prim-apply.hh | 45 + .../tinyusdz_repo/src/tydra/render-data.cc | 6541 ++++++++++ .../tinyusdz_repo/src/tydra/render-data.hh | 1794 +++ .../tinyusdz_repo/src/tydra/scene-access.cc | 2678 ++++ .../tinyusdz_repo/src/tydra/scene-access.hh | 578 + .../tinyusdz_repo/src/tydra/shader-network.cc | 456 + .../tinyusdz_repo/src/tydra/shader-network.hh | 219 + .../tinyusdz_repo/src/tydra/texture-loader.hh | 0 .../tinyusdz_repo/src/tydra/tydra.png | Bin 0 -> 507383 bytes .../tinyusdz_repo/src/unicode-xid-table.inc | 1459 +++ .../tinyusdz/tinyusdz_repo/src/unicode-xid.hh | 65 + contrib/tinyusdz/tinyusdz_repo/src/usdGeom.cc | 865 ++ contrib/tinyusdz/tinyusdz_repo/src/usdGeom.hh | 1176 ++ contrib/tinyusdz/tinyusdz_repo/src/usdLux.cc | 12 + contrib/tinyusdz/tinyusdz_repo/src/usdLux.hh | 208 + contrib/tinyusdz/tinyusdz_repo/src/usdMtlx.cc | 1084 ++ contrib/tinyusdz/tinyusdz_repo/src/usdMtlx.hh | 164 + contrib/tinyusdz/tinyusdz_repo/src/usdObj.cc | 265 + contrib/tinyusdz/tinyusdz_repo/src/usdObj.hh | 28 + .../tinyusdz/tinyusdz_repo/src/usdShade.cc | 146 + .../tinyusdz/tinyusdz_repo/src/usdShade.hh | 331 + contrib/tinyusdz/tinyusdz_repo/src/usdSkel.cc | 191 + contrib/tinyusdz/tinyusdz_repo/src/usdSkel.hh | 361 + .../tinyusdz/tinyusdz_repo/src/usda-reader.cc | 1786 +++ .../tinyusdz/tinyusdz_repo/src/usda-reader.hh | 144 + .../tinyusdz/tinyusdz_repo/src/usda-writer.cc | 91 + .../tinyusdz/tinyusdz_repo/src/usda-writer.hh | 26 + .../tinyusdz/tinyusdz_repo/src/usdc-reader.cc | 3872 ++++++ .../tinyusdz/tinyusdz_repo/src/usdc-reader.hh | 60 + .../tinyusdz/tinyusdz_repo/src/usdc-writer.cc | 597 + .../tinyusdz/tinyusdz_repo/src/usdc-writer.hh | 38 + .../tinyusdz_repo/src/value-eval-util.hh | 1948 +++ .../tinyusdz_repo/src/value-pprint.cc | 1027 ++ .../tinyusdz_repo/src/value-pprint.hh | 370 + .../tinyusdz_repo/src/value-type-macros.inc | 217 + .../tinyusdz/tinyusdz_repo/src/value-types.cc | 1141 ++ .../tinyusdz/tinyusdz_repo/src/value-types.hh | 2705 ++++ contrib/tinyusdz/tinyusdz_repo/src/xform.cc | 1738 +++ contrib/tinyusdz/tinyusdz_repo/src/xform.hh | 219 + doc/Fileformats.md | 1 + test/CMakeLists.txt | 1 + test/models-nonbsd/USD/usda/README.md | 3 + test/models-nonbsd/USD/usda/blendshape.usda | 154 + test/models-nonbsd/USD/usda/texturedcube.usda | 101 + test/models-nonbsd/USD/usda/textures/01.jpg | Bin 0 -> 10290 bytes .../USD/usda/textures/checkerboard.png | Bin 0 -> 7688 bytes .../USD/usda/textures/texture-cat.jpg | Bin 0 -> 50234 bytes .../USD/usda/translated-cube.usda | 55 + test/models-nonbsd/USD/usdc/README.md | 4 + test/models-nonbsd/USD/usdc/blendshape.usdc | Bin 0 -> 4862 bytes test/models-nonbsd/USD/usdc/suzanne.usdc | Bin 0 -> 48768 bytes test/models-nonbsd/USD/usdc/texturedcube.usdc | Bin 0 -> 3551 bytes test/models-nonbsd/USD/usdc/textures/01.jpg | Bin 0 -> 10290 bytes .../USD/usdc/textures/checkerboard.png | Bin 0 -> 7688 bytes .../USD/usdc/textures/texture-cat.jpg | Bin 0 -> 50234 bytes .../USD/usdc/translated-cube.usdc | Bin 0 -> 2527 bytes test/unit/utUSDImport.cpp | 66 + 225 files changed, 146546 insertions(+) create mode 100644 code/AssetLib/USD/USDLoader.cpp create mode 100644 code/AssetLib/USD/USDLoader.h create mode 100644 code/AssetLib/USD/USDLoaderImplTinyusdz.cpp create mode 100644 code/AssetLib/USD/USDLoaderImplTinyusdz.h create mode 100644 code/AssetLib/USD/USDLoaderUtil.cpp create mode 100644 code/AssetLib/USD/USDLoaderUtil.h create mode 100644 contrib/tinyusdz/README.md create mode 100644 contrib/tinyusdz/assimp_tinyusdz_logging.inc create mode 100644 contrib/tinyusdz/tinyusdz_repo/LICENSE create mode 100644 contrib/tinyusdz/tinyusdz_repo/README.md create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/ascii-parser-basetype.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/ascii-parser-timesamples-array.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/ascii-parser-timesamples.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/ascii-parser.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/ascii-parser.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/asset-resolution.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/asset-resolution.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/common-macros.inc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/composition.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/composition.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/crate-format.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/crate-format.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/crate-pprint.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/crate-pprint.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/crate-reader.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/crate-reader.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/define-type-trait.inc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/dtoa_milo.LICENSE create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/dtoa_milo.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/LICENSE-APACHE create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/LICENSE-BOOST create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/LICENSE-MIT create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/README.md create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/ascii_number.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/bigint.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/constexpr_feature_detect.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/decimal_to_binary.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/digit_comparison.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/fast_float.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/fast_table.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/float_common.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/parse_number.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/simple_decimal_conversion.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/LICENSE create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/README.md create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/filesystem.hpp create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_fwd.hpp create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_impl.hpp create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_std.hpp create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_std_fwd.hpp create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_std_impl.hpp create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/LICENSE create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/README.md create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/atof.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/bit_ops.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/cached_power.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/conversion_status.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/default_fallback.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/diy_fp.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/fraction.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/ftoa.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/grisu.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/huge_val.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/integer_of_size.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/k_comp.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/krosh.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/memwrap.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/powers_ten.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/powers_ten_double.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/powers_ten_single.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/prettify.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/print.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/static_pow.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/type_punning_cast.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/fpng.cpp create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/fpng.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/glob/LICENSE create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/glob/README.md create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/glob/include/glob/glob.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/glob/single_include/glob/glob.hpp create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/glob/source/glob.cpp create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/half-edge.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/jsteemann/LICENSE create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/jsteemann/README.md create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/jsteemann/atoi.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/jsteemann/modification.md create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/linalg.README create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/linalg.UNLICENSE create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/linalg.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/earcut/CHANGELOG.md create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/earcut/LICENSE create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/earcut/README.md create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/earcut/earcut.hpp create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/eternal/LICENSE.md create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/eternal/README.md create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/eternal/include/mapbox/eternal.hpp create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/nanoflann.COPYING create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/nanoflann.hpp create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/stb_image.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/stb_image_resize.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/stb_image_resize2.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/external/stb_image_write.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/handle-allocator.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/image-loader.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/image-loader.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/image-types.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/image-util.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/image-util.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/image-writer.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/image-writer.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/integerCoding.cpp create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/integerCoding.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/io-util.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/io-util.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/linear-algebra.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/linear-algebra.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/lz4-compression.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/lz4-compression.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/lz4/LICENSE create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/lz4/lz4.c create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/lz4/lz4.h create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/math-util.inc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/nonstd/expected.hpp create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/nonstd/optional.hpp create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/path-util.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/path-util.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/pprinter.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/pprinter.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/prim-composition.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/prim-pprint.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/prim-reconstruct.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/prim-reconstruct.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/prim-type-macros.inc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/prim-types.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/prim-types.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/primvar.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/primvar.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/stage.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/stage.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/str-util.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/str-util.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/stream-reader.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/stream-writer.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/subdiv.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/subdiv.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/texture-types.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tiny-any.inc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tiny-format.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tiny-format.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tiny-variant.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tinyusdz.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tinyusdz.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/token-type.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/README.md create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval-typed-animatable-fallback.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval-typed-animatable.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval-typed-fallback.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval-typed.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/facial.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/facial.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/nurbs-tess.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/obj-export.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/obj-export.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/prim-apply.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/prim-apply.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/render-data.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/render-data.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/scene-access.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/scene-access.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/shader-network.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/shader-network.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/texture-loader.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/tydra/tydra.png create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/unicode-xid-table.inc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/unicode-xid.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usdGeom.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usdGeom.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usdLux.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usdLux.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usdMtlx.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usdMtlx.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usdObj.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usdObj.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usdShade.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usdShade.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usdSkel.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usdSkel.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usda-reader.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usda-reader.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usda-writer.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usda-writer.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usdc-reader.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usdc-reader.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usdc-writer.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/usdc-writer.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/value-eval-util.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/value-pprint.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/value-pprint.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/value-type-macros.inc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/value-types.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/value-types.hh create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/xform.cc create mode 100644 contrib/tinyusdz/tinyusdz_repo/src/xform.hh create mode 100644 test/models-nonbsd/USD/usda/README.md create mode 100644 test/models-nonbsd/USD/usda/blendshape.usda create mode 100644 test/models-nonbsd/USD/usda/texturedcube.usda create mode 100644 test/models-nonbsd/USD/usda/textures/01.jpg create mode 100644 test/models-nonbsd/USD/usda/textures/checkerboard.png create mode 100644 test/models-nonbsd/USD/usda/textures/texture-cat.jpg create mode 100644 test/models-nonbsd/USD/usda/translated-cube.usda create mode 100644 test/models-nonbsd/USD/usdc/README.md create mode 100644 test/models-nonbsd/USD/usdc/blendshape.usdc create mode 100644 test/models-nonbsd/USD/usdc/suzanne.usdc create mode 100644 test/models-nonbsd/USD/usdc/texturedcube.usdc create mode 100644 test/models-nonbsd/USD/usdc/textures/01.jpg create mode 100644 test/models-nonbsd/USD/usdc/textures/checkerboard.png create mode 100644 test/models-nonbsd/USD/usdc/textures/texture-cat.jpg create mode 100644 test/models-nonbsd/USD/usdc/translated-cube.usdc create mode 100644 test/unit/utUSDImport.cpp diff --git a/code/AssetLib/USD/USDLoader.cpp b/code/AssetLib/USD/USDLoader.cpp new file mode 100644 index 000000000..c79b45f0f --- /dev/null +++ b/code/AssetLib/USD/USDLoader.cpp @@ -0,0 +1,120 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + + All rights reserved. + + Redistribution and use of this software in source and binary forms, + with or without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + + * Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + --------------------------------------------------------------------------- + */ + +/** @file USDLoader.cpp + * @brief Implementation of the USD importer class + */ + +#ifndef ASSIMP_BUILD_NO_USD_IMPORTER +#include + +// internal headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "USDLoader.h" +#include "USDLoaderUtil.h" + +static constexpr aiImporterDesc desc = { + "USD Object Importer", + "", + "", + "https://en.wikipedia.org/wiki/Universal_Scene_Description/", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "usd usda usdc usdz" +}; + +namespace Assimp { +using namespace std; + +// Constructor to be privately used by Importer +USDImporter::USDImporter() : + impl(USDImporterImplTinyusdz()) { +} + +// ------------------------------------------------------------------------------------------------ + +bool USDImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const { + // Based on token + static const uint32_t usdcTokens[] = { AI_MAKE_MAGIC("PXR-USDC") }; + bool canRead = CheckMagicToken(pIOHandler, pFile, usdcTokens, AI_COUNT_OF(usdcTokens)); + if (canRead) { + return canRead; + } + + // Based on extension + if (isUsda(pFile) || isUsdc(pFile)) { + return true; + } + return true; +} + +const aiImporterDesc *USDImporter::GetInfo() const { + return &desc; +} + +void USDImporter::InternReadFile( + const std::string &pFile, + aiScene *pScene, + IOSystem *pIOHandler) { + impl.InternReadFile( + pFile, + pScene, + pIOHandler); +} + +} // namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_USD_IMPORTER diff --git a/code/AssetLib/USD/USDLoader.h b/code/AssetLib/USD/USDLoader.h new file mode 100644 index 000000000..3d6b58db7 --- /dev/null +++ b/code/AssetLib/USD/USDLoader.h @@ -0,0 +1,78 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + + All rights reserved. + + Redistribution and use of this software in source and binary forms, + with or without modification, are permitted provided that the + following conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + + * Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ---------------------------------------------------------------------- + */ + +/** @file USDLoader.h + * @brief Declaration of the USD importer class. + */ +#pragma once +#ifndef AI_USDLOADER_H_INCLUDED +#define AI_USDLOADER_H_INCLUDED + +#include +#include +#include +#include + +#include "USDLoaderImplTinyusdz.h" + +namespace Assimp { +class USDImporter : public BaseImporter { +public: + USDImporter(); + ~USDImporter() override = default; + + /// \brief Returns whether the class can handle the format of the given file. + /// \remark See BaseImporter::CanRead() for details. + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; + +protected: + //! \brief Appends the supported extension. + const aiImporterDesc *GetInfo() const override; + + void InternReadFile( + const std::string &pFile, + aiScene *pScene, + IOSystem *pIOHandler) override; +private: + USDImporterImplTinyusdz impl; +}; + +} // namespace Assimp +#endif // AI_USDLOADER_H_INCLUDED diff --git a/code/AssetLib/USD/USDLoaderImplTinyusdz.cpp b/code/AssetLib/USD/USDLoaderImplTinyusdz.cpp new file mode 100644 index 000000000..0f7c32b11 --- /dev/null +++ b/code/AssetLib/USD/USDLoaderImplTinyusdz.cpp @@ -0,0 +1,643 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + + All rights reserved. + + Redistribution and use of this software in source and binary forms, + with or without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + + * Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + --------------------------------------------------------------------------- + */ + +/** @file USDLoader.cpp + * @brief Implementation of the USD importer class + */ + +#ifndef ASSIMP_BUILD_NO_USD_IMPORTER +#include +#include + +// internal headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "io-util.hh" // namespace tinyusdz::io +#include "tydra/scene-access.hh" +#include "tydra/shader-network.hh" +#include "USDLoaderImplTinyusdz.h" +#include "USDLoaderUtil.h" + +#include "../../../contrib/tinyusdz/assimp_tinyusdz_logging.inc" + +namespace { + const char *const TAG = "USDLoaderImplTinyusdz (C++)"; +} + +namespace Assimp { +using namespace std; + +void USDImporterImplTinyusdz::InternReadFile( + const std::string &pFile, + aiScene *pScene, + IOSystem *pIOHandler) { + // Grab filename for logging purposes + size_t pos = pFile.find_last_of('/'); + string basePath = pFile.substr(0, pos); + string nameWExt = pFile.substr(pos + 1); + (void) TAG; // Ignore unused variable when -Werror enabled + stringstream ss; + ss.str(""); + ss << "InternReadFile(): model" << nameWExt; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + + bool ret{ false }; + tinyusdz::USDLoadOptions options; + tinyusdz::Stage stage; + std::string warn, err; + bool is_usdz{ false }; + if (isUsdc(pFile)) { + ret = LoadUSDCFromFile(pFile, &stage, &warn, &err, options); + ss.str(""); + ss << "InternReadFile(): LoadUSDCFromFile() result: " << ret; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } else if (isUsda(pFile)) { + ret = LoadUSDAFromFile(pFile, &stage, &warn, &err, options); + ss.str(""); + ss << "InternReadFile(): LoadUSDAFromFile() result: " << ret; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } else if (isUsdz(pFile)) { + ret = LoadUSDZFromFile(pFile, &stage, &warn, &err, options); + is_usdz = true; + ss.str(""); + ss << "InternReadFile(): LoadUSDZFromFile() result: " << ret; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } else if (isUsd(pFile)) { + ret = LoadUSDFromFile(pFile, &stage, &warn, &err, options); + ss.str(""); + ss << "InternReadFile(): LoadUSDFromFile() result: " << ret; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } + if (warn.empty() && err.empty()) { + ss.str(""); + ss << "InternReadFile(): load free of warnings/errors"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } else { + if (!warn.empty()) { + ss.str(""); + ss << "InternReadFile(): WARNING reported: " << warn; + TINYUSDZLOGW(TAG, "%s", ss.str().c_str()); + } + if (!err.empty()) { + ss.str(""); + ss << "InternReadFile(): ERROR reported: " << err; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + } + } + if (!ret) { + ss.str(""); + ss << "InternReadFile(): ERROR: load failed! ret: " << ret; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + return; + } + tinyusdz::tydra::RenderScene render_scene; + tinyusdz::tydra::RenderSceneConverter converter; + tinyusdz::tydra::RenderSceneConverterEnv env(stage); + std::string usd_basedir = tinyusdz::io::GetBaseDir(pFile); + env.set_search_paths({ usd_basedir }); // {} needed to convert to vector of char + + // NOTE: Pointer address of usdz_asset must be valid until the call of RenderSceneConverter::ConvertToRenderScene. + tinyusdz::USDZAsset usdz_asset; + if (is_usdz) { + if (!tinyusdz::ReadUSDZAssetInfoFromFile(pFile, &usdz_asset, &warn, &err)) { + if (!warn.empty()) { + ss.str(""); + ss << "InternReadFile(): ReadUSDZAssetInfoFromFile: WARNING reported: " << warn; + TINYUSDZLOGW(TAG, "%s", ss.str().c_str()); + } + if (!err.empty()) { + ss.str(""); + ss << "InternReadFile(): ReadUSDZAssetInfoFromFile: ERROR reported: " << err; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + } + ss.str(""); + ss << "InternReadFile(): ReadUSDZAssetInfoFromFile: ERROR!"; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + } else { + ss.str(""); + ss << "InternReadFile(): ReadUSDZAssetInfoFromFile: OK"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } + + tinyusdz::AssetResolutionResolver arr; + if (!tinyusdz::SetupUSDZAssetResolution(arr, &usdz_asset)) { + ss.str(""); + ss << "InternReadFile(): SetupUSDZAssetResolution: ERROR: load failed! ret: " << ret; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + } else { + ss.str(""); + ss << "InternReadFile(): SetupUSDZAssetResolution: OK"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + env.asset_resolver = arr; + } + } + + ret = converter.ConvertToRenderScene(env, &render_scene); + if (!ret) { + ss.str(""); + ss << "InternReadFile(): ConvertToRenderScene() failed!"; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + return; + } + pScene->mNumMeshes = render_scene.meshes.size(); + ss.str(""); + ss << "InternReadFile(): mNumMeshes: " << pScene->mNumMeshes; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes](); + // Create root node + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; + ss.str(""); + ss << "InternReadFile(): mRootNode->mNumMeshes: " << pScene->mRootNode->mNumMeshes; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + pScene->mRootNode->mMeshes = new unsigned int[pScene->mRootNode->mNumMeshes]; + + // Export meshes + for (size_t meshIdx = 0; meshIdx < pScene->mNumMeshes; meshIdx++) { + pScene->mMeshes[meshIdx] = new aiMesh(); + pScene->mMeshes[meshIdx]->mName.Set(render_scene.meshes[meshIdx].prim_name); + if (render_scene.meshes[meshIdx].material_id > -1) { + pScene->mMeshes[meshIdx]->mMaterialIndex = render_scene.meshes[meshIdx].material_id; + } + verticesForMesh(render_scene, pScene, meshIdx, nameWExt); + facesForMesh(render_scene, pScene, meshIdx, nameWExt); + // Some models infer normals from faces, but others need them e.g. + // - apple "toy car" canopy normals will be wrong + // - human "untitled" model (tinyusdz issue #115) will be "splotchy" + normalsForMesh(render_scene, pScene, meshIdx, nameWExt); + materialsForMesh(render_scene, pScene, meshIdx, nameWExt); + uvsForMesh(render_scene, pScene, meshIdx, nameWExt); + pScene->mRootNode->mMeshes[meshIdx] = static_cast(meshIdx); + } + nodes(render_scene, pScene, nameWExt); + materials(render_scene, pScene, nameWExt); + textures(render_scene, pScene, nameWExt); + textureImages(render_scene, pScene, nameWExt); + buffers(render_scene, pScene, nameWExt); + animations(render_scene, pScene, nameWExt); +} + +void USDImporterImplTinyusdz::verticesForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt) { + pScene->mMeshes[meshIdx]->mNumVertices = render_scene.meshes[meshIdx].points.size(); + pScene->mMeshes[meshIdx]->mVertices = new aiVector3D[pScene->mMeshes[meshIdx]->mNumVertices]; + for (size_t j = 0; j < pScene->mMeshes[meshIdx]->mNumVertices; ++j) { + pScene->mMeshes[meshIdx]->mVertices[j].x = render_scene.meshes[meshIdx].points[j][0]; + pScene->mMeshes[meshIdx]->mVertices[j].y = render_scene.meshes[meshIdx].points[j][1]; + pScene->mMeshes[meshIdx]->mVertices[j].z = render_scene.meshes[meshIdx].points[j][2]; + } +} + +void USDImporterImplTinyusdz::facesForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt) { + pScene->mMeshes[meshIdx]->mNumFaces = render_scene.meshes[meshIdx].faceVertexCounts().size(); + pScene->mMeshes[meshIdx]->mFaces = new aiFace[pScene->mMeshes[meshIdx]->mNumFaces](); + size_t faceVertIdxOffset = 0; +// stringstream ss; +// ss.str(""); +// ss << "facesForMesh() for model " << nameWExt << " mesh[" << meshIdx << "]; " << +// render_scene.meshes[meshIdx].faceVertexIndices().size() << " indices, " << +// render_scene.meshes[meshIdx].faceVertexCounts().size() << " counts, type: " << +// static_cast(render_scene.meshes[meshIdx].vertexArrayType) << +// ", Indexed: " << static_cast(tinyusdz::tydra::RenderMesh::VertexArrayType::Indexed) << +// ", Facevarying: " << static_cast(tinyusdz::tydra::RenderMesh::VertexArrayType::Facevarying); +// TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + if (render_scene.meshes[meshIdx].vertexArrayType == tinyusdz::tydra::RenderMesh::VertexArrayType::Indexed) { +// ss.str(""); +// ss << "vertexArrayType: Indexed"; +// TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } else { +// ss.str(""); +// ss << "vertexArrayType: Facevarying"; +// TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } + for (size_t faceIdx = 0; faceIdx < pScene->mMeshes[meshIdx]->mNumFaces; ++faceIdx) { + pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices = render_scene.meshes[meshIdx].faceVertexCounts()[faceIdx]; + pScene->mMeshes[meshIdx]->mFaces[faceIdx].mIndices = new unsigned int[pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices]; +// ss.str(""); +// ss << " m[" << meshIdx << "] f[" << faceIdx << "] o[" << +// faceVertIdxOffset << "] N: " << pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices << ": "; + for (size_t j = 0; j < pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices; ++j) { +// ss << "i[" << j << "]: " << +// render_scene.meshes[meshIdx].faceVertexIndices()[j + faceVertIdxOffset]; +// if (j < pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices - 1) { +// ss << ", "; +// } + pScene->mMeshes[meshIdx]->mFaces[faceIdx].mIndices[j] = + render_scene.meshes[meshIdx].faceVertexIndices()[j + faceVertIdxOffset]; + } +// TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + faceVertIdxOffset += pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices; + } +} + +void USDImporterImplTinyusdz::normalsForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt) { + stringstream ss; + pScene->mMeshes[meshIdx]->mNormals = new aiVector3D[pScene->mMeshes[meshIdx]->mNumVertices]; + const float *floatPtr = reinterpret_cast(render_scene.meshes[meshIdx].normals.get_data().data()); + ss.str(""); + ss << "normalsForMesh() for model " << nameWExt << " mesh[" << meshIdx << "]: " << + "data size: " << render_scene.meshes[meshIdx].normals.get_data().size() << + ", num verts: " << pScene->mMeshes[meshIdx]->mNumVertices; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + for (size_t vertIdx = 0, fpj = 0; vertIdx < pScene->mMeshes[meshIdx]->mNumVertices; ++vertIdx, fpj += 3) { + pScene->mMeshes[meshIdx]->mNormals[vertIdx].x = floatPtr[fpj]; + pScene->mMeshes[meshIdx]->mNormals[vertIdx].y = floatPtr[fpj + 1]; + pScene->mMeshes[meshIdx]->mNormals[vertIdx].z = floatPtr[fpj + 2]; + } +} + +void USDImporterImplTinyusdz::materialsForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt) { +} + +void USDImporterImplTinyusdz::uvsForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt) { + stringstream ss; + const size_t uvSlotsCount = render_scene.meshes[meshIdx].texcoords.size(); + ss.str(""); + ss << "uvsForMesh(): uvSlotsCount for mesh[" << meshIdx << "]: " << uvSlotsCount << " w/" << + pScene->mMeshes[meshIdx]->mNumVertices << " vertices"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + if (uvSlotsCount < 1) { + return; + } + pScene->mMeshes[meshIdx]->mTextureCoords[0] = new aiVector3D[pScene->mMeshes[meshIdx]->mNumVertices]; + pScene->mMeshes[meshIdx]->mNumUVComponents[0] = 2; // U and V stored in "x", "y" of aiVector3D. + for (size_t uvSlotIdx = 0; uvSlotIdx < uvSlotsCount; ++uvSlotIdx) { + const auto uvsForSlot = render_scene.meshes[meshIdx].texcoords.at(uvSlotIdx); + if (uvsForSlot.get_data().size() == 0) { + ss.str(""); + ss << " NOTICE: uvSlotIdx: " << uvSlotIdx << " has " << uvsForSlot.get_data().size() << " bytes"; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + continue; + } else { + ss.str(""); + ss << " uvSlotIdx: " << uvSlotIdx << " has " << uvsForSlot.get_data().size() << " bytes"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } + const float *floatPtr = reinterpret_cast(uvsForSlot.get_data().data()); + for (size_t vertIdx = 0, fpj = 0; vertIdx < pScene->mMeshes[meshIdx]->mNumVertices; ++vertIdx, fpj += 2) { + pScene->mMeshes[meshIdx]->mTextureCoords[uvSlotIdx][vertIdx].x = floatPtr[fpj]; + pScene->mMeshes[meshIdx]->mTextureCoords[uvSlotIdx][vertIdx].y = floatPtr[fpj + 1]; + } + } +} + +void USDImporterImplTinyusdz::nodes( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + const size_t numNodes{render_scene.nodes.size()}; + (void) numNodes; // Ignore unused variable when -Werror enabled + stringstream ss; + ss.str(""); + ss << "nodes(): model" << nameWExt << ", numNodes: " << numNodes; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); +} + +static aiColor3D *ownedColorPtrFor(const std::array &color) { + aiColor3D *colorPtr = new aiColor3D(); + colorPtr->r = color[0]; + colorPtr->g = color[1]; + colorPtr->b = color[2]; + return colorPtr; +} + +static std::string nameForTextureWithId( + const tinyusdz::tydra::RenderScene &render_scene, + const int targetId) { + stringstream ss; + std::string texName; + for (const auto &image : render_scene.images) { + if (image.buffer_id == targetId) { + texName = image.asset_identifier; + ss.str(""); + ss << "nameForTextureWithId(): found texture " << texName << " with target id " << targetId; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + break; + } + } + ss.str(""); + ss << "nameForTextureWithId(): ERROR! Failed to find texture with target id " << targetId; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + return texName; +} + +static void assignTexture( + const tinyusdz::tydra::RenderScene &render_scene, + const tinyusdz::tydra::RenderMaterial &material, + aiMaterial *mat, + const int textureId, + const int aiTextureType) { + std::string name = nameForTextureWithId(render_scene, textureId); + aiString *texName = new aiString(); + texName->Set(name); + stringstream ss; + ss.str(""); + ss << "assignTexture(): name: " << name; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + // TODO: verify hard-coded '0' index is correct + mat->AddProperty(texName, _AI_MATKEY_TEXTURE_BASE, aiTextureType, 0); +} + +void USDImporterImplTinyusdz::materials( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + const size_t numMaterials{render_scene.materials.size()}; + (void) numMaterials; // Ignore unused variable when -Werror enabled + stringstream ss; + ss.str(""); + ss << "materials(): model" << nameWExt << ", numMaterials: " << numMaterials; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + pScene->mNumMaterials = 0; + if (render_scene.materials.empty()) { + return; + } + pScene->mMaterials = new aiMaterial *[render_scene.materials.size()]; + for (const auto &material : render_scene.materials) { + ss.str(""); + ss << " material[" << pScene->mNumMaterials << "]: name: |" << material.name << "|, disp name: |" << material.display_name << "|"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + aiMaterial *mat = new aiMaterial; + + aiString *materialName = new aiString(); + materialName->Set(material.name); + mat->AddProperty(materialName, AI_MATKEY_NAME); + + mat->AddProperty( + ownedColorPtrFor(material.surfaceShader.diffuseColor.value), + 1, AI_MATKEY_COLOR_DIFFUSE); + mat->AddProperty( + ownedColorPtrFor(material.surfaceShader.specularColor.value), + 1, AI_MATKEY_COLOR_SPECULAR); + mat->AddProperty( + ownedColorPtrFor(material.surfaceShader.emissiveColor.value), + 1, AI_MATKEY_COLOR_EMISSIVE); + + ss.str(""); + if (material.surfaceShader.diffuseColor.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.diffuseColor.textureId, aiTextureType_DIFFUSE); + ss << " material[" << pScene->mNumMaterials << "]: diff tex id " << material.surfaceShader.diffuseColor.textureId << "\n"; + } + if (material.surfaceShader.specularColor.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.specularColor.textureId, aiTextureType_SPECULAR); + ss << " material[" << pScene->mNumMaterials << "]: spec tex id " << material.surfaceShader.specularColor.textureId << "\n"; + } + if (material.surfaceShader.normal.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.normal.textureId, aiTextureType_NORMALS); + ss << " material[" << pScene->mNumMaterials << "]: normal tex id " << material.surfaceShader.normal.textureId << "\n"; + } + if (material.surfaceShader.emissiveColor.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.emissiveColor.textureId, aiTextureType_EMISSIVE); + ss << " material[" << pScene->mNumMaterials << "]: emissive tex id " << material.surfaceShader.emissiveColor.textureId << "\n"; + } + if (material.surfaceShader.occlusion.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.occlusion.textureId, aiTextureType_LIGHTMAP); + ss << " material[" << pScene->mNumMaterials << "]: lightmap (occlusion) tex id " << material.surfaceShader.occlusion.textureId << "\n"; + } + if (material.surfaceShader.metallic.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.metallic.textureId, aiTextureType_METALNESS); + ss << " material[" << pScene->mNumMaterials << "]: metallic tex id " << material.surfaceShader.metallic.textureId << "\n"; + } + if (material.surfaceShader.roughness.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.roughness.textureId, aiTextureType_DIFFUSE_ROUGHNESS); + ss << " material[" << pScene->mNumMaterials << "]: roughness tex id " << material.surfaceShader.roughness.textureId << "\n"; + } + if (material.surfaceShader.clearcoat.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.clearcoat.textureId, aiTextureType_CLEARCOAT); + ss << " material[" << pScene->mNumMaterials << "]: clearcoat tex id " << material.surfaceShader.clearcoat.textureId << "\n"; + } + if (material.surfaceShader.opacity.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.opacity.textureId, aiTextureType_OPACITY); + ss << " material[" << pScene->mNumMaterials << "]: opacity tex id " << material.surfaceShader.opacity.textureId << "\n"; + } + if (material.surfaceShader.displacement.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.displacement.textureId, aiTextureType_DISPLACEMENT); + ss << " material[" << pScene->mNumMaterials << "]: displacement tex id " << material.surfaceShader.displacement.textureId << "\n"; + } + if (material.surfaceShader.clearcoatRoughness.is_texture()) { + ss << " material[" << pScene->mNumMaterials << "]: clearcoatRoughness tex id " << material.surfaceShader.clearcoatRoughness.textureId << "\n"; + } + if (material.surfaceShader.opacityThreshold.is_texture()) { + ss << " material[" << pScene->mNumMaterials << "]: opacityThreshold tex id " << material.surfaceShader.opacityThreshold.textureId << "\n"; + } + if (material.surfaceShader.ior.is_texture()) { + ss << " material[" << pScene->mNumMaterials << "]: ior tex id " << material.surfaceShader.ior.textureId << "\n"; + } + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + + pScene->mMaterials[pScene->mNumMaterials] = mat; + ++pScene->mNumMaterials; + } +} + +void USDImporterImplTinyusdz::textures( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + const size_t numTextures{render_scene.textures.size()}; + (void) numTextures; // Ignore unused variable when -Werror enabled + stringstream ss; + ss.str(""); + ss << "textures(): model" << nameWExt << ", numTextures: " << numTextures; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + size_t i{0}; + (void) i; + for (const auto &texture : render_scene.textures) { + (void) texture; + ss.str(""); + ss << " texture[" << i << "]: id: " << texture.texture_image_id << ", disp name: |" << texture.display_name << "|, varname_uv: " << + texture.varname_uv << ", prim_name: |" << texture.prim_name << "|, abs_path: |" << texture.abs_path << "|"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + ++i; + } +} + +/** + * "owned" as in, used "new" to allocate and aiScene now responsible for "delete" + * + * @param render_scene renderScene object + * @param image textureImage object + * @param nameWExt filename w/ext (use to extract file type hint) + * @return aiTexture ptr + */ +static aiTexture *ownedEmbeddedTextureFor( + const tinyusdz::tydra::RenderScene &render_scene, + const tinyusdz::tydra::TextureImage &image, + const std::string &nameWExt) { + stringstream ss; + aiTexture *tex = new aiTexture(); + size_t pos = image.asset_identifier.find_last_of('/'); + string embTexName{image.asset_identifier.substr(pos + 1)}; + tex->mFilename.Set(image.asset_identifier.c_str()); + tex->mHeight = image.height; +// const size_t imageBytesCount{render_scene.buffers[image.buffer_id].data.size() / image.channels}; + tex->mWidth = image.width; + if (tex->mHeight == 0) { + pos = embTexName.find_last_of('.'); + strncpy(tex->achFormatHint, embTexName.substr(pos + 1).c_str(), 3); + const size_t imageBytesCount{render_scene.buffers[image.buffer_id].data.size()}; + tex->pcData = (aiTexel *) new char[imageBytesCount]; + memcpy(tex->pcData, &render_scene.buffers[image.buffer_id].data[0], imageBytesCount); + } else { + strncpy(tex->achFormatHint, "rgba8888", 8); + const size_t imageTexelsCount{tex->mWidth * tex->mHeight}; + tex->pcData = (aiTexel *) new char[imageTexelsCount * image.channels]; + const float *floatPtr = reinterpret_cast(&render_scene.buffers[image.buffer_id].data[0]); + ss.str(""); + ss << "ownedEmbeddedTextureFor(): manual fill..."; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + for (int i = 0, fpi = 0; i < imageTexelsCount; ++i, fpi += 4) { + tex->pcData[i].b = static_cast(floatPtr[fpi] * 255); + tex->pcData[i].g = static_cast(floatPtr[fpi + 1] * 255); + tex->pcData[i].r = static_cast(floatPtr[fpi + 2] * 255); + tex->pcData[i].a = static_cast(floatPtr[fpi + 3] * 255); + } + ss.str(""); + ss << "ownedEmbeddedTextureFor(): imageTexelsCount: " << imageTexelsCount << ", channels: " << image.channels; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } + return tex; +} + +void USDImporterImplTinyusdz::textureImages( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + stringstream ss; + const size_t numTextureImages{render_scene.images.size()}; + (void) numTextureImages; // Ignore unused variable when -Werror enabled + ss.str(""); + ss << "textureImages(): model" << nameWExt << ", numTextureImages: " << numTextureImages; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + pScene->mTextures = nullptr; // Need to iterate over images before knowing if valid textures available + pScene->mNumTextures = 0; + for (const auto &image : render_scene.images) { + ss.str(""); + ss << " image[" << pScene->mNumTextures << "]: |" << image.asset_identifier << "| w: " << image.width << ", h: " << image.height << + ", channels: " << image.channels << ", miplevel: " << image.miplevel << ", buffer id: " << image.buffer_id << "\n" << + " buffers.size(): " << render_scene.buffers.size() << ", data empty? " << render_scene.buffers[image.buffer_id].data.empty(); + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + if (image.buffer_id > -1 && + image.buffer_id < render_scene.buffers.size() && + !render_scene.buffers[image.buffer_id].data.empty()) { + aiTexture *tex = ownedEmbeddedTextureFor( + render_scene, + image, + nameWExt); + if (pScene->mTextures == nullptr) { + ss.str(""); + ss << " Init pScene->mTextures[" << render_scene.images.size() << "]"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + pScene->mTextures = new aiTexture *[render_scene.images.size()]; + } + ss.str(""); + ss << " pScene->mTextures[" << pScene->mNumTextures << "] name: |" << tex->mFilename.C_Str() << + "|, w: " << tex->mWidth << ", h: " << tex->mHeight << ", hint: " << tex->achFormatHint; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + pScene->mTextures[pScene->mNumTextures++] = tex; + } + } +} + +void USDImporterImplTinyusdz::buffers( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + const size_t numBuffers{render_scene.buffers.size()}; + (void) numBuffers; // Ignore unused variable when -Werror enabled + stringstream ss; + ss.str(""); + ss << "buffers(): model" << nameWExt << ", numBuffers: " << numBuffers; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + size_t i = 0; + for (const auto &buffer : render_scene.buffers) { + ss.str(""); + ss << " buffer[" << i << "]: count: " << buffer.data.size() << ", type: " << to_string(buffer.componentType); + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + ++i; + } +} + +void USDImporterImplTinyusdz::animations( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + const size_t numAnimations{render_scene.animations.size()}; + (void) numAnimations; // Ignore unused variable when -Werror enabled + stringstream ss; + ss.str(""); + ss << "animations(): model" << nameWExt << ", numAnimations: " << numAnimations; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); +} + +} // namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_USD_IMPORTER diff --git a/code/AssetLib/USD/USDLoaderImplTinyusdz.h b/code/AssetLib/USD/USDLoaderImplTinyusdz.h new file mode 100644 index 000000000..c31303733 --- /dev/null +++ b/code/AssetLib/USD/USDLoaderImplTinyusdz.h @@ -0,0 +1,127 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + + All rights reserved. + + Redistribution and use of this software in source and binary forms, + with or without modification, are permitted provided that the + following conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + + * Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ---------------------------------------------------------------------- + */ + +/** @file USDLoader.h + * @brief Declaration of the USD importer class. + */ +#pragma once +#ifndef AI_USDLOADER_IMPL_TINYUSDZ_H_INCLUDED +#define AI_USDLOADER_IMPL_TINYUSDZ_H_INCLUDED + +#include +#include +#include +#include +#include "tinyusdz.hh" +#include "tydra/render-data.hh" + +namespace Assimp { +class USDImporterImplTinyusdz { +public: + USDImporterImplTinyusdz() = default; + ~USDImporterImplTinyusdz() = default; + + void InternReadFile( + const std::string &pFile, + aiScene *pScene, + IOSystem *pIOHandler); + + void verticesForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt); + + void facesForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt); + + void normalsForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt); + + void materialsForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt); + + void uvsForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt); + + void nodes( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + void materials( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + void textures( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + void textureImages( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + void buffers( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + void animations( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); +}; +} // namespace Assimp +#endif // AI_USDLOADER_IMPL_TINYUSDZ_H_INCLUDED diff --git a/code/AssetLib/USD/USDLoaderUtil.cpp b/code/AssetLib/USD/USDLoaderUtil.cpp new file mode 100644 index 000000000..16f0ba8ae --- /dev/null +++ b/code/AssetLib/USD/USDLoaderUtil.cpp @@ -0,0 +1,116 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + + All rights reserved. + + Redistribution and use of this software in source and binary forms, + with or without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + + * Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + --------------------------------------------------------------------------- + */ + +/** @file USDLoader.cpp + * @brief Implementation of the USD importer class + */ + +#ifndef ASSIMP_BUILD_NO_USD_IMPORTER +#include + +// internal headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "USDLoaderUtil.h" + +namespace Assimp { +using namespace std; +bool isUsda(const std::string &pFile) { + size_t pos = pFile.find_last_of('.'); + if (pos == string::npos) { + return false; + } + string ext = pFile.substr(pos + 1); + if (ext.size() != 4) { + return false; + } + return (ext[0] == 'u' || ext[0] == 'U') && (ext[1] == 's' || ext[1] == 'S') && (ext[2] == 'd' || ext[2] == 'D') && (ext[3] == 'a' || ext[3] == 'A'); +} + +bool isUsdc(const std::string &pFile) { + size_t pos = pFile.find_last_of('.'); + if (pos == string::npos) { + return false; + } + string ext = pFile.substr(pos + 1); + if (ext.size() != 4) { + return false; + } + return (ext[0] == 'u' || ext[0] == 'U') && (ext[1] == 's' || ext[1] == 'S') && (ext[2] == 'd' || ext[2] == 'D') && (ext[3] == 'c' || ext[3] == 'C'); +} + +bool isUsdz(const std::string &pFile) { + size_t pos = pFile.find_last_of('.'); + if (pos == string::npos) { + return false; + } + string ext = pFile.substr(pos + 1); + if (ext.size() != 4) { + return false; + } + return (ext[0] == 'u' || ext[0] == 'U') && (ext[1] == 's' || ext[1] == 'S') && (ext[2] == 'd' || ext[2] == 'D') && (ext[3] == 'z' || ext[3] == 'Z'); +} + +bool isUsd(const std::string &pFile) { + size_t pos = pFile.find_last_of('.'); + if (pos == string::npos) { + return false; + } + string ext = pFile.substr(pos + 1); + if (ext.size() != 3) { + return false; + } + return (ext[0] == 'u' || ext[0] == 'U') && (ext[1] == 's' || ext[1] == 'S') && (ext[2] == 'd' || ext[2] == 'D'); +} +} // namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_USD_IMPORTER diff --git a/code/AssetLib/USD/USDLoaderUtil.h b/code/AssetLib/USD/USDLoaderUtil.h new file mode 100644 index 000000000..b39cd254e --- /dev/null +++ b/code/AssetLib/USD/USDLoaderUtil.h @@ -0,0 +1,59 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + + All rights reserved. + + Redistribution and use of this software in source and binary forms, + with or without modification, are permitted provided that the + following conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + + * Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ---------------------------------------------------------------------- + */ + +/** @file USDLoader.h + * @brief Declaration of the USD importer class. + */ +#pragma once +#ifndef AI_USDLOADER_UTIL_H_INCLUDED +#define AI_USDLOADER_UTIL_H_INCLUDED + +#include +#include +#include +#include + +namespace Assimp { +bool isUsda(const std::string &pFile); +bool isUsdc(const std::string &pFile); +bool isUsdz(const std::string &pFile); +bool isUsd(const std::string &pFile); +} // namespace Assimp +#endif // AI_USDLOADER_UTIL_H_INCLUDED diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index b397d5ec4..5b59d6284 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -812,6 +812,15 @@ ADD_ASSIMP_IMPORTER( 3D AssetLib/Unreal/UnrealLoader.h ) +ADD_ASSIMP_IMPORTER( USD + AssetLib/USD/USDLoader.cpp + AssetLib/USD/USDLoader.h + AssetLib/USD/USDLoaderImplTinyusdz.cpp + AssetLib/USD/USDLoaderImplTinyusdz.h + AssetLib/USD/USDLoaderUtil.cpp + AssetLib/USD/USDLoaderUtil.h +) + ADD_ASSIMP_IMPORTER( X AssetLib/X/XFileHelper.h AssetLib/X/XFileImporter.cpp @@ -905,6 +914,85 @@ SET( Extra_SRCS ) SOURCE_GROUP( Extra FILES ${Extra_SRCS}) +# USD/USDA/USDC/USDZ support +# tinyusdz +IF (NOT ASSIMP_BUILD_NO_USD_IMPORTER) + set(Tinyusdz_SRC_RELPATH "../contrib/tinyusdz/tinyusdz_repo/src") + set(Tinyusdz_SRCS + ${Tinyusdz_SRC_RELPATH}/ascii-parser.cc + ${Tinyusdz_SRC_RELPATH}/ascii-parser-basetype.cc + ${Tinyusdz_SRC_RELPATH}/ascii-parser-timesamples.cc + ${Tinyusdz_SRC_RELPATH}/ascii-parser-timesamples-array.cc + ${Tinyusdz_SRC_RELPATH}/asset-resolution.cc + ${Tinyusdz_SRC_RELPATH}/composition.cc + ${Tinyusdz_SRC_RELPATH}/crate-format.cc + ${Tinyusdz_SRC_RELPATH}/crate-pprint.cc + ${Tinyusdz_SRC_RELPATH}/crate-reader.cc + ${Tinyusdz_SRC_RELPATH}/image-loader.cc + ${Tinyusdz_SRC_RELPATH}/image-util.cc + ${Tinyusdz_SRC_RELPATH}/image-writer.cc + ${Tinyusdz_SRC_RELPATH}/io-util.cc + ${Tinyusdz_SRC_RELPATH}/linear-algebra.cc + ${Tinyusdz_SRC_RELPATH}/path-util.cc + ${Tinyusdz_SRC_RELPATH}/pprinter.cc + ${Tinyusdz_SRC_RELPATH}/prim-composition.cc + ${Tinyusdz_SRC_RELPATH}/prim-reconstruct.cc + ${Tinyusdz_SRC_RELPATH}/prim-types.cc + ${Tinyusdz_SRC_RELPATH}/primvar.cc + ${Tinyusdz_SRC_RELPATH}/stage.cc + ${Tinyusdz_SRC_RELPATH}/str-util.cc + ${Tinyusdz_SRC_RELPATH}/tiny-format.cc + ${Tinyusdz_SRC_RELPATH}/tinyusdz.cc + ${Tinyusdz_SRC_RELPATH}/tydra/attribute-eval.cc + ${Tinyusdz_SRC_RELPATH}/tydra/attribute-eval-typed.cc + ${Tinyusdz_SRC_RELPATH}/tydra/attribute-eval-typed-animatable.cc + ${Tinyusdz_SRC_RELPATH}/tydra/attribute-eval-typed-animatable-fallback.cc + ${Tinyusdz_SRC_RELPATH}/tydra/attribute-eval-typed-fallback.cc + ${Tinyusdz_SRC_RELPATH}/tydra/facial.cc + ${Tinyusdz_SRC_RELPATH}/tydra/prim-apply.cc + ${Tinyusdz_SRC_RELPATH}/tydra/render-data.cc + ${Tinyusdz_SRC_RELPATH}/tydra/scene-access.cc + ${Tinyusdz_SRC_RELPATH}/tydra/shader-network.cc + ${Tinyusdz_SRC_RELPATH}/usda-reader.cc + ${Tinyusdz_SRC_RELPATH}/usda-writer.cc + ${Tinyusdz_SRC_RELPATH}/usdc-reader.cc + ${Tinyusdz_SRC_RELPATH}/usdc-writer.cc + ${Tinyusdz_SRC_RELPATH}/usdGeom.cc + ${Tinyusdz_SRC_RELPATH}/usdLux.cc + ${Tinyusdz_SRC_RELPATH}/usdMtlx.cc + ${Tinyusdz_SRC_RELPATH}/usdShade.cc + ${Tinyusdz_SRC_RELPATH}/usdSkel.cc + ${Tinyusdz_SRC_RELPATH}/value-pprint.cc + ${Tinyusdz_SRC_RELPATH}/value-types.cc + ${Tinyusdz_SRC_RELPATH}/xform.cc + ) + + set(Tinyusdz_DEP_SOURCES + ${Tinyusdz_SRC_RELPATH}/external/fpng.cpp + #${Tinyusdz_SRC_RELPATH}/external/staticstruct.cc + #${Tinyusdz_SRC_RELPATH}/external/string_id/database.cpp + #${Tinyusdz_SRC_RELPATH}/external/string_id/error.cpp + #${Tinyusdz_SRC_RELPATH}/external/string_id/string_id.cpp + #${Tinyusdz_SRC_RELPATH}/external/tinyxml2/tinyxml2.cpp + ${Tinyusdz_SRC_RELPATH}/integerCoding.cpp + ${Tinyusdz_SRC_RELPATH}/lz4-compression.cc + ${Tinyusdz_SRC_RELPATH}/lz4/lz4.c + ) + + set(tinyusdz_INCLUDE_DIRS "../contrib/tinyusdz/tinyusdz_repo/src") + INCLUDE_DIRECTORIES(${tinyusdz_INCLUDE_DIRS}) + SOURCE_GROUP( Contrib\\Tinyusdz + FILES + ${Tinyusdz_SRCS} + ${Tinyusdz_DEP_SOURCES} + ) + MESSAGE(STATUS "tinyusdz enabled") +ELSE() + set(Tinyusdz_SRCS "") + set(Tinyusdz_DEP_SOURCES "") + MESSAGE(STATUS "tinyusdz disabled") +ENDIF() + # pugixml IF(ASSIMP_HUNTER_ENABLED) hunter_add_package(pugixml) @@ -1155,6 +1243,8 @@ SET( assimp_src ${openddl_parser_SRCS} ${open3dgc_SRCS} ${ziplib_SRCS} + ${Tinyusdz_SRCS} + ${Tinyusdz_DEP_SOURCES} ${Pugixml_SRCS} ${stb_SRCS} # Necessary to show the headers in the project when using the VC++ generator: diff --git a/code/Common/ImporterRegistry.cpp b/code/Common/ImporterRegistry.cpp index 92a04e692..276f20974 100644 --- a/code/Common/ImporterRegistry.cpp +++ b/code/Common/ImporterRegistry.cpp @@ -55,6 +55,9 @@ corresponding preprocessor flag to selectively disable formats. // Importers // (include_new_importers_here) // ------------------------------------------------------------------------------------------------ +#if !defined(ASSIMP_BUILD_NO_USD_IMPORTER) +#include "AssetLib/USD/USDLoader.h" +#endif #ifndef ASSIMP_BUILD_NO_X_IMPORTER #include "AssetLib/X/XFileImporter.h" #endif @@ -230,6 +233,9 @@ void GetImporterInstanceList(std::vector &out) { // (register_new_importers_here) // ---------------------------------------------------------------------------- out.reserve(64); +#if !defined(ASSIMP_BUILD_NO_USD_IMPORTER) + out.push_back(new USDImporter()); +#endif #if (!defined ASSIMP_BUILD_NO_X_IMPORTER) out.push_back(new XFileImporter()); #endif diff --git a/contrib/tinyusdz/README.md b/contrib/tinyusdz/README.md new file mode 100644 index 000000000..60cfe9f40 --- /dev/null +++ b/contrib/tinyusdz/README.md @@ -0,0 +1,40 @@ +# tinyusdz + +## Notes +Couldn't leverage tinyusdz CMakeLists.txt. Fell back to compiling source files specified in +"android" example. + +## Assimp update history +### Apr 2024 +Updated to [tinyusdz](https://github.com/syoyo/tinyusdz) branch `rendermesh-refactor` at 18 Mar 2024 commit `f9792ce67c4ef08d779cdf91f49ad97acc426466 ` + +### Mar 2024 +Cloned github project [tinyusdz](https://github.com/syoyo/tinyusdz) branch `dev` at 10 Mar 2024 commit `912d27e8b632d2352e7284feb86584832c6015d5` + +Removed folders: +- [.clusterfuzzlite](tinyusdz_repo%2F.clusterfuzzlite) +- [.git](tinyusdz_repo%2F.git) +- [.github](tinyusdz_repo%2F.github) +- [android](tinyusdz_repo%2Fandroid) +- [benchmarks](tinyusdz_repo%2Fbenchmarks) +- [cmake](tinyusdz_repo%2Fcmake) +- [data](tinyusdz_repo%2Fdata) +- [doc](tinyusdz_repo%2Fdoc) +- [examples](tinyusdz_repo%2Fexamples) +- [models](tinyusdz_repo%2Fmodels) +- [python](tinyusdz_repo%2Fpython) +- [sandbox](tinyusdz_repo%2Fsandbox) +- [schema](tinyusdz_repo%2Fschema) +- [scripts](tinyusdz_repo%2Fscripts) +- [tests](tinyusdz_repo%2Ftests) + +Removed folders in `src`: +- [attic](tinyusdz_repo%2Fsrc%2Fattic) +- [blender](tinyusdz_repo%2Fsrc%2Fblender) +- [osd](tinyusdz_repo%2Fsrc%2Fosd) + +Removed unused `.cc` files in `src`, `external` etc + +Removed all files at root level except +- [LICENSE](tinyusdz_repo%2FLICENSE) +- [README.md](tinyusdz_repo%2FREADME.md) diff --git a/contrib/tinyusdz/assimp_tinyusdz_logging.inc b/contrib/tinyusdz/assimp_tinyusdz_logging.inc new file mode 100644 index 000000000..08ea5d5bd --- /dev/null +++ b/contrib/tinyusdz/assimp_tinyusdz_logging.inc @@ -0,0 +1,54 @@ +/** + * Usage + * Add line below all other #include statements: + * #include "../../../assimp_tinyusdz_logging.inc" + * to files: + * - contrib/tinyusdz/tinyusdz_repo/src/tydra/render-data.cc + * - contrib/tinyusdz/tinyusdz_repo/src/tydra/scene-access.cc + */ +#pragma once + +#if defined(__ANDROID__) +#include +#include + +#define TINYUSDZLOGT(tag, ...) ((void)__android_log_print(ANDROID_LOG_DEBUG, tag, __VA_ARGS__)) +#define TINYUSDZLOG0(tag, ...) ((void)__android_log_print(ANDROID_LOG_DEFAULT, tag, __VA_ARGS__)) +#define TINYUSDZLOGD(tag, ...) ((void)__android_log_print(ANDROID_LOG_DEBUG, tag, __VA_ARGS__)) +#define TINYUSDZLOGI(tag, ...) ((void)__android_log_print(ANDROID_LOG_INFO, tag, __VA_ARGS__)) +#define TINYUSDZLOGW(tag, ...) ((void)__android_log_print(ANDROID_LOG_WARN, tag, __VA_ARGS__)) +#define TINYUSDZLOGE(tag, ...) ((void)__android_log_print(ANDROID_LOG_ERROR, tag, __VA_ARGS__)) +#else +#define TINYUSDZLOGT(tag, ...) +#define TINYUSDZLOG0(tag, ...) +#define TINYUSDZLOGD(tag, ...) +#define TINYUSDZLOGI(tag, ...) +#define TINYUSDZLOGW(tag, ...) +#define TINYUSDZLOGE(tag, ...) +#endif // #if defined(__ANDROID__) + +#if defined(TINYUSDZ_LOCAL_DEBUG_PRINT) +#if defined(__ANDROID__) +// Works well but _extremely_ verbose +#define DCOUT(x) \ + do { \ + std::stringstream ss; \ + ss << __FILE__ << ":" << __func__ << ":" \ + << std::to_string(__LINE__) << " " << x << "\n"; \ + TINYUSDZLOGE("tinyusdz", "%s", ss.str().c_str()); \ + } while (false) +// Silent version +//#define DCOUT(x) \ +// do { \ +// std::stringstream ss; \ +// ss << __FILE__ << ":" << __func__ << ":" \ +// << std::to_string(__LINE__) << " " << x << "\n"; \ +// } while (false) +#else +#define DCOUT(x) \ + do { \ + std::cout << __FILE__ << ":" << __func__ << ":" \ + << std::to_string(__LINE__) << " " << x << "\n"; \ + } while (false) +#endif // #if defined(__ANDROID__) +#endif // #if defined(TINYUSDZ_LOCAL_DEBUG_PRINT) \ No newline at end of file diff --git a/contrib/tinyusdz/tinyusdz_repo/LICENSE b/contrib/tinyusdz/tinyusdz_repo/LICENSE new file mode 100644 index 000000000..f4dd9c6c4 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/LICENSE @@ -0,0 +1,16 @@ +Apache 2.0 License + +Copyright (c) 2020-2023 Syoyo Fujita +Copyright (c) 2024-Present Light Transport Entertainment Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/contrib/tinyusdz/tinyusdz_repo/README.md b/contrib/tinyusdz/tinyusdz_repo/README.md new file mode 100644 index 000000000..9d991b21e --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/README.md @@ -0,0 +1,529 @@ +# Tiny USDZ/USDA/USDC library in C++14 + +`TinyUSDZ` is secure, portable and dependency-free(depends only on C++ STL. Other 3rd-party libraries included. Yes, you don't need pxrUSD/OpenUSD library!) USDZ/USDC/USDA library written in C++14. + +## High priority + +* Tydra: Handy data structure converter for rendering https://github.com/syoyo/tinyusdz/issues/31 + * Working on the branch: https://github.com/syoyo/tinyusdz/tree/rendermesh-refactor + * [ ] USD to RenderScene(OpenGL/Vulkan) conversion https://github.com/syoyo/tinyusdz/issues/109 + * [ ] GeomSubset/Material Binding API support for shading/texturing https://github.com/syoyo/tinyusdz/issues/103 + * [ ] UTF8 Identifier support https://github.com/syoyo/tinyusdz/issues/47 + +## Mid-term todo + +* Collection API + * [ ] https://github.com/syoyo/tinyusdz/issues/108 +* Experimental composition support https://github.com/syoyo/tinyusdz/issues/25 + * [x] subLayers + * [x] references + * [x] payload(no delayed load) + * [x] inherits + * [x] variantSet + * [ ] Validate composition is correctly operated. +* Better usdLux support https://github.com/syoyo/tinyusdz/issues/101 +* [ ] Support parsing usd-wg USD aasets + * https://github.com/syoyo/tinyusdz/issues/135 +* Support reading & compose some production USD scenes + * [ ] Moana island v2.1 https://github.com/syoyo/tinyusdz/issues/90 + * [ ] ALAB USD production scene https://github.com/syoyo/tinyusdz/issues/91 + +* MaterialX https://github.com/syoyo/tinyusdz/issues/86 + * USD + MateriralX + PBR rendering example using https://github.com/lighttransport/pbrlab +* Improve interoperability with Blender USD export/import https://github.com/syoyo/tinyusdz/issues/98 +* Example viewer + * [examples/openglviewer](examples/openglviewer) OpenGL viewer + * [examples/sdlviewer](examples/sdlviewer) Software raytracing viewer + +## "What if" Experimental feature + +* Gaussian Splatting support? https://repo-sam.inria.fr/fungraph/3d-gaussian-splatting/ + +## Build status + +| | Linux | Windows | macOS | iOS | Android | +|:-------:|:---------------------------------------- |:------------------------------------- |:---------:|:------:|:-------:| +| dev | [![Linux Build](https://github.com/syoyo/tinyusdz/actions/workflows/linux_ci.yml/badge.svg)](https://github.com/syoyo/tinyusdz/actions/workflows/linux_ci.yml) | [![Windows CI build](https://github.com/syoyo/tinyusdz/actions/workflows/windows_ci.yml/badge.svg)](https://github.com/syoyo/tinyusdz/actions/workflows/windows_ci.yml)
[![Windows ARM CI build](https://github.com/syoyo/tinyusdz/actions/workflows/windows_arm_ci.yml/badge.svg)](https://github.com/syoyo/tinyusdz/actions/workflows/windows_arm_ci.yml) | [![macOS Build](https://github.com/syoyo/tinyusdz/actions/workflows/macos_ci.yml/badge.svg)](https://github.com/syoyo/tinyusdz/actions/workflows/macos_ci.yml) | [![iOS Build](https://github.com/syoyo/tinyusdz/actions/workflows/ios_ci.yml/badge.svg)](https://github.com/syoyo/tinyusdz/actions/workflows/ios_ci.yml) | [![Android arm64v8a Build](https://github.com/syoyo/tinyusdz/actions/workflows/android_ci.yml/badge.svg)](https://github.com/syoyo/tinyusdz/actions/workflows/android_ci.yml) | + +## Supported platforms + +| | Linux | Windows | macOS | iOS | Android | WASM(WASI) | WASM(Emscripten) | +|:-------:|:---------------------------------------- |:------------------------------------- |:---------:|:------:|:-------:|:------------------------------:|:-----------:| +| dev | ✅ 64bit
✅ 32bit
✅ aarch64 | ✅ 64bit
✅ 32bit
✅ ARM64/ARM32 |✅ |✅ |✅ |✅ [sandbox/wasi](sandbox/wasi) | ✅ [sandbox/emscripten](sandbox/emscripten) | + +### Python binding(testing. currently not working) + +https://pypi.org/project/tinyusdz/ + +Python binding is very early alpha testing stage. Not working at the moment. + +You can install Python prebuilt wheel using + +``` +$ python -m pip install tinyusdz +``` + +| | Linux | Windows | macOS 11(Big Sur) or later | macos 10 | +|:-------:|:---------------------------------------- |:------------------------------------- |:-----------------------------:|:---------:| +| 3.6(⚠️) | ✅ 64bit
✅ 32bit
✅ aarch64 | ✅ 64bit
✅ 32bit
✅ ARM64 |🚫 | ✅ Intel | +| 3.7 | ✅ 64bit
✅ 32bit
✅ aarch64 | ✅ 64bit
✅ 32bit
✅ ARM64 |✅ arm64 | 🚫 universal2
✅ Intel | +| 3.8 | ✅ 64bit
✅ 32bit
✅ aarch64 | ✅ 64bit
✅ 32bit
✅ ARM64 |✅ arm64 | ✅ universal2
✅ Intel | +| 3.9 | ✅ 64bit
✅ 32bit
✅ aarch64 | ✅ 64bit
✅ 32bit
✅ ARM64 |✅ arm64 | ✅ universal2
✅ Intel | +| 3.10 | ✅ 64bit
✅ 32bit
✅ aarch64 | ✅ 64bit
✅ 32bit
✅ ARM64 |✅ arm64 | ✅ universal2
✅ Intel | +| 3.11 | ✅ 64bit
✅ 32bit
✅ aarch64 | ✅ 64bit
✅ 32bit
✅ ARM64 |✅ arm64 | ✅ universal2
✅ Intel | + +⚠️ Python 3.6 is EOL and not recommended to use it. 3.6 bwheels is provided as long as cibuildwheels provides the build for it. +NOTE: Windows ARM64 binary is provided using cross-compiling. Its not well tested. + + +## Status + +TinyUSDZ is in v0.8.0 release candidate. +Core loading feature(both USDA and USDC) is now working and production-grade(And no seg fault for corrupted USDA/USDC/USDZ input). + +v0.8.0 is Flattened scene only(i.e, USDA/USDC generated by using pxrUSD's `usdcat --flatten` or USDZ scene). +Composition features are work-in-progress(experimental Composition feature support in v0.8.0. Better composition feature in next major release v0.9.0(Q4/2023 expected) planned) + +Remaining tasks for v0.8.0 release are writing examples, demos and utility functions(Tydra. Especially access to Material/Shader attributes). + +* [x] USDZ/USDC(Crate) parser + * USDC Crate version v0.8.0(most commonly used version as of 2022 Nov) or higher is supported. +* [ ] USDZ/USDC(Crate) writer (Work-in-progress) +* [x] USDA parser(Hand-written from a scratch. No Bison/Flex dependency!) +* [x] USDA writer +* [x] Support basic Primitives(Xform, Mesh, BasisCurves, etc.), basic Lights and Shaders(UsdPreviewSurface, UsdUVTexture, UsdPrimvarReader) +* **Experimental** support of some Composition features https://github.com/syoyo/tinyusdz/issues/25 + * [x] subLayers + * [x] references + * [x] payload + * [x] inherits + * [x] variants + * [ ] specializes + +**Please see** [doc/status.md](doc/status.md) **for more details** + +* [ ] Basic C API(`c-tinyusd`) for language bindings + * [ ] [examples/c_api_example](examples/c_api_example) + * [ ] Basic Python binding +* [ ] Write simple SDL viewer example(2023 Winter expected) +* [ ] Write iOS and Android example(2023 Winter expected) +* [ ] Write Vision OS example?(2024 expected) +* [ ] Vulkan or OptiX/HIP RT raytracing viewer example +* [ ] USD <-> glTF converter example + * There is an independent work of USD to glTF binary GLB converter: https://github.com/fynv/usd2glb +* [ ] Web demo with Three.js? + * [ ] Three.js started to support USDZ with Ascii format, but no USDC support yet: https://github.com/mrdoob/three.js/issues/14219 + +## Discussions + +We've opened Github Discussions page! https://github.com/syoyo/tinyusdz/discussions + +### Security and memory budget + +TinyUSDZ has first priority of considering security and stability. + +USDZ(USDC) is a binary format. To avoid out-of-bounds access, out-of-memory, and other security issues when loading malcious USDZ(e.g. USDZ file from unknown origin), TinyUSDZ has a memory budget feature to avoid out-of-memory issue. + +To limit a memory usage when loading USDZ file, Please set a value `max_memory_limit_in_mb` in USDLoadOptions. + +TinyUSDZ source codes(and some external third party codes) are also checked by Address Sanitizer, CodeQL and Fuzzer. + +#### Fuzzer + +See [tests/fuzzer](tests/fuzzer) . +For building fuzzer tests, you'll need Meson and Ninja. + +#### Web platform(WASM) and sandboxed environment(WASI) + +If you need to deal with arbitrary USD files from unknown origin(e.g. from internet, NFT storage. Whose may contain malcious data), it is recommended to use TinyUSDZ in sandboxed environment(RunC, FlatPak, WASI(WASM)). Run in WASI is recommended at the moment. + +TinyUSDZ does not use C++ exceptions and can be built without threads. TinyUSDZ supports WASM and WASI build. So TinyUSDZ should runs well on various Web platform(WebAssembly. No SharedArrayBuffer, Atomics and WebAssembly SIMD(which is not yet available on iOS Safari) required) and sandboxed environment(WASI. Users who need to read various USD file which possibly could contain malcious data from Internet, IPFS or blockchain storage). + +See [sandbox/wasi/](sandbox/wasi) for Building TinyUSDZ with WASI toolchain. + +### Tydra + +USD itself is a generic container of 3D scene data. + +Tydra is an interface to Renderers/Viewers and other DCCs. +Tydra may be something like Tiny version of pxrUSD Hydra, but its API is completely different. See [src/tydra/README.md](src/tydra/README.md) for the background. + +* Image color space + * sRGB + * Linear + * Rec.709 + * [ ] Partial support of OCIO(OpenColor IO) through TinyColorIO https://github.com/syoyo/tinycolorio . Currently SPI3DLut only. +* More details are T.B.W. + +## Notice + +TinyUSDZ does not support Reality Composer file format(`.reality`) since it uses proprietary file format and we cannot understand it(so no conversion support from/to Reality also). + +## Commercial support + +TinyUSDZ focuses on loading/writing USDA/USDC/USDZ functionalities. +Example viewer is just for demo purpose. +`syoyo` does not provide commercial support as an individual. + +If you need commercial support, eco-system development(e.g. plug-ins, DCC tools on top of TinyUSDZ) or production-grade USDZ model viewer(e.g. embed TinyUSDZ to your AR app, 3D NFT Android mobile viewer capable of displaying (encrypted) USDZ model), please contact Light Transport Entertainment, Inc. : https://goo.gl/forms/1p6uGcOKWGpXPHkA2 + +We have a plan to manage TinyUSDZ project under Light Transport Entertainment Inc. +(By relicensing to Apatch 2.0) + +## Projects using TinyUSDZ + +* usd2glb: USD to glTF 2.0 GLB converter https://github.com/fynv/usd2glb +* webgpu-cpp-usdz: WebGPU C++/Wasm USDZ Renderer(NOTE: It doesn't support much yet!) https://github.com/Twinklebear/webgpu-cpp-usdz + +### Other related projects + +* UsdzSharpie: C# Simple implementation of Usdz file format ( https://github.com/UkooLabs/UsdzSharpie ) +* TinyGLTF: glTF 2.0 loader/saver ( https://github.com/syoyo/tinygltf ) +* BlenderUSDZ: It contains their own Python USDC parser/serializer. https://github.com/robmcrosby/BlenderUSDZ + +## Supported platforms + +* [x] Linux 64bit or later + * [x] ARM AARCH64 + * [x] x86-64 + * [ ] RISC-V(Should work) + * [ ] SPARC, POWER(Big endian machine). May work(theoretically) +* [x] Android arm64v8a +* [x] iOS +* [x] macOS(Arm, x86-64) +* [x] Windows 10 64bit or later + * [x] Windows ARM + * [x] clang-cl + MSVC SDK cross compile +* [x] WebAssembly + * Emscripten + * See [examples/sdlviewer/](examples/sdlviewer) example. +* [x] WASI(through WASI toolchain) + * See [sandbox/wasi](sandbox/wasi) + +## Requirements + +* C++14 compiler + * [x] gcc 4.9 or later + * [x] Visual Studio 2019 or later(2017 may compiles) + * VS2019 16.10 or later you can use `CMakePresets.json` for easier building. + * [x] Can be compiled with standalone MSVC compilers(Build Tools for Visual Studio 2019) + * [x] clang 3.4 or later https://clang.llvm.org/cxx_status.html + * [x] llvm-mingw(clang) supported + * [x] MinGW gcc supported, but not recommended(You may got compilation failure depending on your build configuration: https://github.com/syoyo/tinyusdz/issues/33 , and linking takes too much time if you use default bfd linker.). If you want to compile TinyUSDZ in MinGW environment, llvm-mingw(clang) is recommended to use. + + +Compilation with C++17 is also supported. +Compile on C++20 and C++23 could be possible, but not well tested, since C++20/C++23 compiler is not yet mature(as of 2024/01)) + +## Build + +### Integrate to your app + +If you are using CMake, just include tinyusdz repo with `add_subdirectory` and set include path to `/src` +We recommend to use CMake 3.24 or later. +(Mininum requirement is 3.16) + +```cmake + +... + +# TinyUSDZ examples, tests and tools builds are disabled by default when +# tinyusdz is being built as a library with `add_subdirectory` +add_subdirectory(/path/to/tinyusdz tinyusdz) + +target_include_directories(YOUR_APP PRIVATE "/path/to/tinyusdz/src") + +# Namespaced static library target `tinyusdz::tinyusdz_static` is provided. +# At the moment we recommend to use static build of TinyUSDZ. +# You can use alias target `tinyusdz_static` also for legacy cmake version. +target_link_libraries(YOUR_APP PRIVATE tinyusdz::tinyusdz_static) + +# For TinyUSDZ DLL(shared) library target, you can use +# `tinyusdz` library target +``` + +Another way is simply copy `src` folder to your app, and add `*.cc` files to your app's build system. +All include paths are set relative from `src` folder, so you can just add include directory to `src` folder. + +See `/CMakeLists.txt` and [examples/sdlviewer/CMakeLists.txt](examples/sdlviewer/CMakeLists.txt) for details. + +TinyUSDZ does not generate any header files and source files before the build and after the build(before the installation stage), so you don't need to take care of any pre-processing and post-processing of source tree. For example, USD Ascii parser uses hand-written C++ code so no Bison/flex/PEG processing involved. + +It may not be recommend to use tinyusdz as a git submodule, since the repo contains lots of codes required to build TinyUSDZ examples but these are not required for your app. + +### Compiler defines + +Please see `CMake build options` and `CMakeLists.txt`. In most case same identifier is defined from cmake build options: For example if you specify `-DTINYUSDZ_PRODUCTION_BUILD=1` for cmake argument, `TINYUSDZ_PRODUCTION_BUILD` is defined. + +### CMake + +Cmake build is provided. + +#### Linux and macOS + +``` +$ mkdir build +$ cd build +$ cmake .. +$ make +``` + +Please take a look at `scripts/bootstrap-cmake-*.sh` for some build configuraions. + +#### Visual Studio + +Visual Studio 2019 and 2022 are supported. + +`CMakePresets.json` is provided for easier build on Visual Studio 2019 and Visual Studio 2022, but has lot of limitations(and seems parallel build is not working well so build is slow). + +If you want flexibility, ordinary cmake `.sln` generation approach by invoking `vcsetup.bat` recommended. +(Edit VS version in `vcsetup.bat` as you with before the run) + +#### LLVM-MinGW build + +MinGW native and cross-compiling example using llvm-mingw(clang) is provided. +See `scripts/bootstrap-cmake-mingw-win.sh` and `scripts/bootstrap-cmake-llvm-mingw-cross.sh` for details. + +One of benefit to use llvm-mingw is address sanitizer support on Windows app. + +To run app(`.exe`, you'll need `libunwind.dll` and `libc++.dll` on your working directory or search path) + +For Windows native build, we assume `ninja.exe` is installed on your system(You can use it from Meson package) + +#### CMake build options + +* `TINYUSDZ_PRODUCTION_BUILD` : Production build. Do not output debugging logs. +* `TINYUSDZ_BUILD_TESTS` : Build tests +* `TINYUSDZ_BUILD_EXAMPLES` : Build examples(note that not all examples in `examples` folder are built) +* `TINYUSDZ_WITH_OPENSUBDIV` : Use OpenSubviv to tessellate subdivision surface. + * OpenSubdiv code is included in TinyUSDZ repo. If you want to use external OpenSubdiv repo, specity the path to OpenSubdiv using `osd_DIR` cmake environment variable. +* `TINYUSDZ_WITH_AUDIO` : Support loading audio(mp3 and wav). +* `TINYUSDZ_WITH_EXR` : Support loading EXR format HDR texture through TinyEXR. +* `TINYUSDZ_WITH_PXR_COMPAT_API` : Build with pxrUSD compatible API. + +#### clang-cl on Windows + +Assume ninja.exe is installed and path to ninja.exe is added to your `%PATH%` + +Edit path to MSVC SDK and Windows SDK in `bootstrap-clang-cl-win64.bat`, then + +``` +> bootstrap-clang-cl-win64.bat +> ninja.exe +``` + + +### Tools and Examples + +* [tusdcat](examples/tusdcat/) Parse USDZ/USDA/USDC and print it as Ascii(similar to `usdcat` in pxrUSD). + * `tusdcat` also do USD composition(`flatten`) and contains TinyUSDZ Composition API usecase. +* Deprecated. Use `tusdcat` [usda_parser](examples/usda_parser/) Parse USDA and print it as Ascii. +* Deprecated. Use `tusdcat` [usdc_parser](examples/usdc_parser/) Parse USDC and print it as Ascii. +* [Simple SDL viewer](examples/sdlviewer/) + * Separated CMake build provided: See [Readme](examples/sdlviewer/README.md) +* [api_tutorial](examples/api_tutorial/) Tutorial of TinyUSDZ Core API to construct a USD scene data. +* [tydra_api](examples/tydra_api/) Tutorial of TinyUSDZ Tydra API to access/query/convert a USD scene data. +* [asset_resolution](examples/asset_resolution/) Tutorial of using AssetResolutionResolver API to load USD from customized I/O(e.g. from Memory, Web, DB, ...) +* [file_format](examples/file_format/) Tutorial of using custom FileFormat handler to load Prim data in custom fileformat. + +See [examples](examples) directory for more examples, but may not actively maintained except for the above examples. + +### USDZ Data format + +See [prim_format.md](doc/prim_format.md) and [preview_surface.md](doc/preview_surface.md) + +## Example + +### Minimum example to load USDA/USDC/USDZ file. + +``` +// TinyUSDZ is not a header-only library, so no TINYUSDZ_IMPLEMENTATIONS +#include "tinyusdz.hh" + +// Include pprinter.hh and value-pprint.hh if you want to print TinyUSDZ classes/structs/enums. +// `tinyusdz::to_string()` and `std::operator<<` for TinyUSDZ classes/enums are provided separately for faster compilation +#include +#include "pprinter.hh" +#include "value-pprint.hh" + +int main(int argc, char **argv) { + + std::string filename = "input.usd"; + + if (argc > 1) { + filename = argv[1]; + } + + tinyusdz::Stage stage; // Stage in USD terminology is nearly meant for Scene in generic 3D graphics terminology. + std::string warn; + std::string err; + + // Auto detect USDA/USDC/USDZ + bool ret = tinyusdz::LoadUSDFromFile(filename, &stage, &warn, &err); + + if (warn.size()) { + std::cout << "WARN : " << warn << "\n"; + } + + if (!ret) { + if (!err.empty()) { + std::cerr << "ERR : " << warn << "\n"; + } + return EXIT_FAILURE; + } + + // Print Stage(Scene graph) + std::cout << tinyusdz::to_string(stage) << "\n"; + + // You can also use ExportToString() as done in pxrUSD + // std::cout << stage.ExportToString() << "\n"; + + // stage.metas() To get Scene metadatum, + for (const Prim &root_prim : stage.root_prims()) { + std::cout << root_prim.absolute_path() << "\n"; + // You can traverse Prim(scene graph object) using Prim::children() + // See examples/api_tutorial and examples/tydra_api for details. + } + + return EXIT_SUCCESS; +} +``` + +### With Core TinyUSDZ API + +Please see [api_tutorial](examples/api_tutorial/) + +### With Tydra API + +Please see [tydra_api](examples/tydra_api/) + + +## TODO + +### Higher priority + +* [ ] Built-in usdObj(wavefront .obj mesh) support. + * via tinyobjloader. +* [x] Support Crate(binary) version 0.8.0(USD v20.11 default) +* [ ] usdSkel utilities + * [ ] Joint hierachy reconstruction and compute skinning matrix(usdSkel) + * [ ] Blend shapes + * [x] Basic Blendshapes support + * [ ] In-between blend shapes +* [ ] Read USD data with bounded memory size. This feature is especially useful for mobile platform(e.g. in terms of security, memory consumption, etc) +* [ ] USDC writer +* [ ] Support Nested USDZ +* [ ] UDIM texture support +* [ ] MaterialX support + * [ ] Parse XML file using tinyxml2 + +### Middle priority + +* [ ] Composition arcs +* [ ] Code refactoring, code optimization + +### Lower priority + +* [ ] iOS example? +* [ ] Support AR related schema(Game-like feature added by Reality Composer?) +* [ ] Audio play support + * [ ] Play audio using SoLoud or miniaudio(or Oboe for Android) + * [ ] wav(dr_wav) + * [ ] mp3(dr_mp3) + * [ ] m4a(ALAC?) +* [ ] Viewer with Vulkan API. +* [ ] Replace OpenSubdiv with our own subdiv library. +* [ ] Write parser based on the schema definition. +* [ ] Support big endian architecture. + +## Python binding and prebuit packages + +Python binding and prebuilt packages(uploadded on PyPI) are provided. + +See [python/README.md](python/README.md) and [doc/python_binding.md](doc/python_binding.md) for details. + +## CI build + +CI build script is a build script trying to build TinyUSDZ in self-contained manner as much as possible(including custom Python build) + +### Linux/macOS + +T.B.W. + +### Windows + +Build custom Python, + +``` +> ci-build-python-lib.bat +``` + +then build TinyUSDZ by linking with this local Python build. + +``` +> ci-build-vs2022.bat +``` + +#### Cross compile with clang-cl + MSVC SDK on linux and run it on WINE(No Windows required at all solution!) + +clang-cl(MSVC cl.exe) + MSVC SDK cross compile is also supported. + +Please take a look at [doc/wine_cl.md](doc/wine_cl.md) + +You can build pure Windows build of TinyUSDZ on Linux CI machine. + +## License + +TinyUSDZ is primarily licensed under Apache 2.0 license. +Some helper code is licensed under MIT license. + +### Third party licenses + +* pxrUSD : Apache 2.0 license. https://github.com/PixarAnimationStudios/USD +* OpenSubdiv : Apache 2.0 license. https://github.com/PixarAnimationStudios/OpenSubdiv +* lz4 : BSD-2 license. http://www.lz4.org +* cnpy(uncompressed ZIP decode/encode code) : MIT license https://github.com/rogersce/cnpy +* tinyexr: BSD license. +* tinyobjloader: MIT license. +* tinygltf: MIT license. +* tinycolorio: MIT license. https://github.com/syoyo/tinycolorio +* stb_image, stb_image_resize, stb_image_write, stb_truetype: public domain. +* dr_libs: public domain. https://github.com/mackron/dr_libs +* miniaudio: public domain or MIT no attribution. https://github.com/dr-soft/miniaudio +* SDL2 : zlib license. https://www.libsdl.org/index.php +* optional-lite: BSL 1.0 license. https://github.com/martinmoene/optional-lite +* expected-lite: BSL 1.0 license. https://github.com/martinmoene/expected-lite +* mapbox/earcut.hpp: ISC license. https://github.com/mapbox/earcut.hpp +* par_shapes.h generate parametric surfaces and other simple shapes: MIT license https://github.com/prideout/par +* MaterialX: Apache 2.0 license. https://github.com/AcademySoftwareFoundation/MaterialX +* string_id: zlib license. https://github.com/foonathan/string_id +* cityhash: MIT license. https://github.com/google/cityhash +* fast_float: Apache 2.0/MIT dual license. https://github.com/fastfloat/fast_float +* jsteeman/atoi: Apache 2.0 license. https://github.com/jsteemann/atoi +* formatxx: unlicense. https://github.com/seanmiddleditch/formatxx +* ubench.h: Unlicense. https://github.com/sheredom/ubench.h +* thelink2012/any : BSL-1.0 license. https://github.com/thelink2012/any +* simple_match : BSL-1.0 license. https://github.com/jbandela/simple_match +* nanobind : BSD-3 license. https://github.com/wjakob/nanobind +* pybind11 : BSD-3 license. https://github.com/pybind/pybind11 +* pystring : BSD-3 license. https://github.com/imageworks/pystring +* gulrak/filesytem : MIT license. https://github.com/gulrak/filesystem +* p-ranav/glob : MIT license. https://github.com/p-ranav/glob +* linalg.h : Unlicense. https://github.com/sgorsten/linalg +* mapbox/eternal: ISC License. https://github.com/mapbox/eternal +* bvh: MIT license. https://github.com/madmann91/bvh +* dtoa_milo.h: MIT License. https://github.com/miloyip/dtoa-benchmark +* jeaiii/itoa: MIT License. https://github.com/jeaiii/itoa +* alac: Apache 2.0 License. https://macosforge.github.io/alac/ +* OpenFBX: MIT License. https://github.com/nem0/OpenFBX +* floaxie: Apache 2.0 License. https://github.com/aclex/floaxie +* boost math sin_pi/cos_pi: BSL 1.0 License. https://www.boost.org/ +* Vulkan: MIT License. https://github.com/SaschaWillems/Vulkan +* Metal.cpp: Apache 2.0 License. https://github.com/bkaradzic/metal-cpp https://developer.apple.com/metal/cpp/ +* sRGB transform: MIT license. https://www.nayuki.io/page/srgb-transform-library +* virtualGizmo3D: BSD-2 license. https://github.com/BrutPitt/virtualGizmo3D +* nanozlib: Apache 2.0 license. https://github.com/lighttransport/nanozlib +* lz4.py: MIT license. https://github.com/SE2Dev/PyCoD/blob/master/_lz4.py +* pugixml: MIT license. https://github.com/zeux/pugixml +* nanoflann: 2-clause BSD license. https://github.com/jlblancoc/nanoflann +* tinymeshutils: MIT license. https://github.com/syoyo/tinymeshutils diff --git a/contrib/tinyusdz/tinyusdz_repo/src/ascii-parser-basetype.cc b/contrib/tinyusdz/tinyusdz_repo/src/ascii-parser-basetype.cc new file mode 100644 index 000000000..05cc3c40a --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/ascii-parser-basetype.cc @@ -0,0 +1,3304 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2021 - 2022, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment, Inc. +// +// Ascii Basic type parser +// + +#include +#ifdef _MSC_VER +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__wasi__) +#else +#include +#include +#endif +#include + +#include "ascii-parser.hh" +#include "str-util.hh" +#include "path-util.hh" +#include "tiny-format.hh" + +// +#if !defined(TINYUSDZ_DISABLE_MODULE_USDA_READER) + +// + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +// external + +#include "external/fast_float/include/fast_float/fast_float.h" +#include "external/jsteemann/atoi.h" +//#include "external/simple_match/include/simple_match/simple_match.hpp" +#include "nonstd/expected.hpp" + +// + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// + +// Tentative +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wunused-parameter" +#endif + +#include "io-util.hh" +#include "pprinter.hh" +#include "prim-types.hh" +#include "str-util.hh" +#include "stream-reader.hh" +#include "tinyusdz.hh" +#include "value-pprint.hh" +#include "value-types.hh" + +#include "common-macros.inc" + +namespace tinyusdz { + +namespace ascii { + +constexpr auto kAscii = "[ASCII]"; + +namespace { + +// parseInt +// 0 = success +// -1 = bad input +// -2 = overflow +// -3 = underflow +int parseInt(const std::string &s, int *out_result) { + size_t n = s.size(); + const char *c = s.c_str(); + + if ((c == nullptr) || (*c) == '\0') { + return -1; + } + + size_t idx = 0; + bool negative = c[0] == '-'; + if ((c[0] == '+') | (c[0] == '-')) { + idx = 1; + if (n == 1) { + // sign char only + return -1; + } + } + + int64_t result = 0; + + // allow zero-padded digits(e.g. "003") + while (idx < n) { + if ((c[idx] >= '0') && (c[idx] <= '9')) { + int digit = int(c[idx] - '0'); + result = result * 10 + digit; + } else { + // bad input + return -1; + } + + if (negative) { + if ((-result) < (std::numeric_limits::min)()) { + return -3; + } + } else { + if (result > (std::numeric_limits::max)()) { + return -2; + } + } + + idx++; + } + + if (negative) { + (*out_result) = -int(result); + } else { + (*out_result) = int(result); + } + + return 0; // OK +} + +nonstd::expected ParseFloat(const std::string &s) { + + // Parse with fast_float + float result; + auto ans = fast_float::from_chars(s.data(), s.data() + s.size(), result); + if (ans.ec != std::errc()) { + // Current `fast_float` implementation does not report detailed parsing err. + return nonstd::make_unexpected("Parse failed."); + } + + return result; +} + +nonstd::expected ParseDouble(const std::string &s) { + + // Parse with fast_float + double result; + auto ans = fast_float::from_chars(s.data(), s.data() + s.size(), result); + if (ans.ec != std::errc()) { + // Current `fast_float` implementation does not report detailed parsing err. + return nonstd::make_unexpected("Parse failed."); + } + + return result; +} + +} // namespace + +// +// -- Parse Basic Type +// +bool AsciiParser::ParseMatrix(value::matrix2f *result) { + + if (!Expect('(')) { + return false; + } + + std::vector> content; + if (!SepBy1TupleType(',', &content)) { + return false; + } + + if (content.size() != 2) { + PushError("# of rows in matrix2f must be 2, but got " + + std::to_string(content.size()) + "\n"); + return false; + } + + if (!Expect(')')) { + return false; + } + + for (size_t i = 0; i < 2; i++) { + result->m[i][0] = content[i][0]; + result->m[i][1] = content[i][1]; + } + + return true; +} + +bool AsciiParser::ParseMatrix(value::matrix3f *result) { + + if (!Expect('(')) { + return false; + } + + std::vector> content; + if (!SepBy1TupleType(',', &content)) { + return false; + } + + if (content.size() != 3) { + PushError("# of rows in matrix3f must be 3, but got " + + std::to_string(content.size()) + "\n"); + return false; + } + + if (!Expect(')')) { + return false; + } + + for (size_t i = 0; i < 3; i++) { + result->m[i][0] = content[i][0]; + result->m[i][1] = content[i][1]; + result->m[i][2] = content[i][2]; + } + + return true; +} + +bool AsciiParser::ParseMatrix(value::matrix4f *result) { + + if (!Expect('(')) { + return false; + } + + std::vector> content; + if (!SepBy1TupleType(',', &content)) { + return false; + } + + if (content.size() != 4) { + PushError("# of rows in matrix4f must be 4, but got " + + std::to_string(content.size()) + "\n"); + return false; + } + + if (!Expect(')')) { + return false; + } + + for (size_t i = 0; i < 4; i++) { + result->m[i][0] = content[i][0]; + result->m[i][1] = content[i][1]; + result->m[i][2] = content[i][2]; + result->m[i][3] = content[i][3]; + } + + return true; +} + +bool AsciiParser::ParseMatrix(value::matrix2d *result) { + + if (!Expect('(')) { + return false; + } + + std::vector> content; + if (!SepBy1TupleType(',', &content)) { + return false; + } + + if (content.size() != 2) { + PushError("# of rows in matrix2d must be 2, but got " + + std::to_string(content.size()) + "\n"); + return false; + } + + if (!Expect(')')) { + return false; + } + + for (size_t i = 0; i < 2; i++) { + result->m[i][0] = content[i][0]; + result->m[i][1] = content[i][1]; + } + + return true; +} + +bool AsciiParser::ParseMatrix(value::matrix3d *result) { + + if (!Expect('(')) { + return false; + } + + std::vector> content; + if (!SepBy1TupleType(',', &content)) { + return false; + } + + if (content.size() != 3) { + PushError("# of rows in matrix3d must be 3, but got " + + std::to_string(content.size()) + "\n"); + return false; + } + + if (!Expect(')')) { + return false; + } + + for (size_t i = 0; i < 3; i++) { + result->m[i][0] = content[i][0]; + result->m[i][1] = content[i][1]; + result->m[i][2] = content[i][2]; + } + + return true; +} + +bool AsciiParser::ParseMatrix(value::matrix4d *result) { + + if (!Expect('(')) { + return false; + } + + std::vector> content; + if (!SepBy1TupleType(',', &content)) { + return false; + } + + if (content.size() != 4) { + PushError("# of rows in matrix4d must be 4, but got " + + std::to_string(content.size()) + "\n"); + return false; + } + + if (!Expect(')')) { + return false; + } + + for (size_t i = 0; i < 4; i++) { + result->m[i][0] = content[i][0]; + result->m[i][1] = content[i][1]; + result->m[i][2] = content[i][2]; + result->m[i][3] = content[i][3]; + } + + return true; +} + +bool AsciiParser::ReadBasicType(Path *value) { + if (value) { + std::string str; + if (!ReadPathIdentifier(&str)) { + return false; + } + + (*value) = pathutil::FromString(str); + + return true; + } else { + return false; + } +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + Path v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::matrix2d *value) { + if (value) { + return ParseMatrix(value); + } else { + return false; + } +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::matrix2d v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::matrix3d *value) { + if (value) { + return ParseMatrix(value); + } else { + return false; + } +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::matrix3d v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::matrix4d *value) { + if (value) { + return ParseMatrix(value); + } else { + return false; + } +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::matrix4d v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::matrix2f *value) { + if (value) { + return ParseMatrix(value); + } else { + return false; + } +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::matrix2f v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::matrix3f *value) { + if (value) { + return ParseMatrix(value); + } else { + return false; + } +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::matrix3f v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::matrix4f *value) { + if (value) { + return ParseMatrix(value); + } else { + return false; + } +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::matrix4f v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +/// +/// Parse the array of tuple. some may be None(e.g. `float3`: [(0, 1, 2), +/// None, (2, 3, 4), ...] ) +/// +template +bool AsciiParser::ParseTupleArray( + std::vector>> *result) { + + if (!Expect('[')) { + return false; + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + // Empty array? + { + char c; + if (!Char1(&c)) { + return false; + } + + if (c == ']') { + result->clear(); + return true; + } + + Rewind(1); + } + + if (!SepBy1TupleType(',', result)) { + return false; + } + + if (!Expect(']')) { + return false; + } + + return true; +} + +// instanciations +template bool AsciiParser::ParseTupleArray(std::vector>> *result); +template bool AsciiParser::ParseTupleArray(std::vector>> *result); +template bool AsciiParser::ParseTupleArray(std::vector>> *result); +template bool AsciiParser::ParseTupleArray(std::vector>> *result); +template bool AsciiParser::ParseTupleArray(std::vector>> *result); +template bool AsciiParser::ParseTupleArray(std::vector>> *result); +template bool AsciiParser::ParseTupleArray(std::vector>> *result); +template bool AsciiParser::ParseTupleArray(std::vector>> *result); +template bool AsciiParser::ParseTupleArray(std::vector>> *result); +template bool AsciiParser::ParseTupleArray(std::vector>> *result); +template bool AsciiParser::ParseTupleArray(std::vector>> *result); +template bool AsciiParser::ParseTupleArray(std::vector>> *result); +//template bool AsciiParser::ParseTupleArray(std::vector>> *result); +//template bool AsciiParser::ParseTupleArray(std::vector>> *result); +//template bool AsciiParser::ParseTupleArray(std::vector>> *result); +//template bool AsciiParser::ParseTupleArray(std::vector>> *result); +//template bool AsciiParser::ParseTupleArray(std::vector>> *result); +//template bool AsciiParser::ParseTupleArray(std::vector>> *result); + +/// +/// Parse the array of tuple(e.g. `float3`: [(0, 1, 2), (2, 3, 4), ...] ) +/// +template +bool AsciiParser::ParseTupleArray(std::vector> *result) { + (void)result; + + if (!Expect('[')) { + return false; + } + + if (!SepBy1TupleType(',', result)) { + return false; + } + + if (!Expect(']')) { + return false; + } + + return true; +} + +bool AsciiParser::ReadBasicType(Identifier *value) { + return ReadIdentifier(value); +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + Identifier v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::token *value) { + // Try triple-quotated string first. + { + value::StringData sdata; + if (MaybeTripleQuotedString(&sdata)) { + // TODO: preserve quotation info. + (*value) = value::token(sdata.value); + return true; + } + } + + std::string s; + if (!ReadStringLiteral(&s)) { + PUSH_ERROR_AND_RETURN_TAG(kAscii, "Failed to parse string literal."); + return false; + } + + (*value) = value::token(s); + return true; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::token v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(std::string *value) { + if (!value) { + return false; + } + + // May be triple-quoted string + { + value::StringData sdata; + if (MaybeTripleQuotedString(&sdata)) { + (*value) = sdata.value; + return true; + + } else if (MaybeString(&sdata)) { + (*value) = sdata.value; + return true; + } + } + + // Just in case + return ReadStringLiteral(value); +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + std::string v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::StringData *value) { + if (!value) { + return false; + } + + // May be triple-quoted string + { + value::StringData sdata; + if (MaybeTripleQuotedString(&sdata)) { + (*value) = sdata; + return true; + + } else if (MaybeString(&sdata)) { + (*value) = sdata; + return true; + } + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::StringData v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(PathIdentifier *value) { + return ReadPathIdentifier(value); +} + +bool AsciiParser::ReadBasicType(bool *value) { + // 'true', 'false', '0' or '1' + { + std::string tok; + + auto loc = CurrLoc(); + bool ok = ReadIdentifier(&tok); + + if (ok) { + if (tok == "true") { + (*value) = true; + return true; + } else if (tok == "false") { + (*value) = false; + return true; + } + } + + // revert + SeekTo(loc); + } + + char sc; + if (!Char1(&sc)) { + return false; + } + _curr_cursor.col++; + + // sign or [0-9] + if (sc == '0') { + (*value) = false; + return true; + } else if (sc == '1') { + (*value) = true; + return true; + } else { + PushError("'0' or '1' expected.\n"); + return false; + } +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + bool v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(int *value) { + std::stringstream ss; + + // pxrUSD allow floating-point value to `int` type. + // so first try fp parsing. + auto loc = CurrLoc(); + std::string fp_str; + if (LexFloat(&fp_str)) { + auto flt = ParseDouble(fp_str); + if (!flt) { + PUSH_ERROR_AND_RETURN("Failed to parse floating value."); + } else { + (*value) = int(flt.value()); + return true; + } + } + + // revert + SeekTo(loc); + + // head character + bool has_sign = false; + // bool negative = false; + { + char sc; + if (!Char1(&sc)) { + return false; + } + _curr_cursor.col++; + + // sign or [0-9] + if (sc == '+') { + // negative = false; + has_sign = true; + } else if (sc == '-') { + // negative = true; + has_sign = true; + } else if ((sc >= '0') && (sc <= '9')) { + // ok + } else { + PushError("Sign or 0-9 expected, but got '" + std::to_string(sc) + + "'.\n"); + return false; + } + + ss << sc; + } + + while (!Eof()) { + char c; + if (!Char1(&c)) { + return false; + } + + if ((c >= '0') && (c <= '9')) { + ss << c; + } else { + _sr->seek_from_current(-1); + break; + } + } + + if (has_sign && (ss.str().size() == 1)) { + // sign only + PushError("Integer value expected but got sign character only.\n"); + return false; + } + + if ((ss.str().size() > 1) && (ss.str()[0] == '0')) { + PushError("Zero padded integer value is not allowed.\n"); + return false; + } + + // std::cout << "ReadInt token: " << ss.str() << "\n"; + + int int_value; + int err = parseInt(ss.str(), &int_value); + if (err != 0) { + if (err == -1) { + PushError("Invalid integer input: `" + ss.str() + "`\n"); + return false; + } else if (err == -2) { + PushError("Integer overflows: `" + ss.str() + "`\n"); + return false; + } else if (err == -3) { + PushError("Integer underflows: `" + ss.str() + "`\n"); + return false; + } else { + PushError("Unknown parseInt error.\n"); + return false; + } + } + + (*value) = int_value; + + return true; +} + +bool AsciiParser::ReadBasicType(value::int2 *value) { + return ParseBasicTypeTuple(value); +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::int2 v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::int3 *value) { + return ParseBasicTypeTuple(value); +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::int3 v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::int4 *value) { + return ParseBasicTypeTuple(value); +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::int4 v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::uint2 *value) { + return ParseBasicTypeTuple(value); +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::uint2 v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::uint3 *value) { + return ParseBasicTypeTuple(value); +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::uint3 v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::uint4 *value) { + return ParseBasicTypeTuple(value); +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::uint4 v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + + +bool AsciiParser::ReadBasicType(uint32_t *value) { + std::stringstream ss; + + // head character + bool has_sign = false; + bool negative = false; + { + char sc; + if (!Char1(&sc)) { + return false; + } + _curr_cursor.col++; + + // sign or [0-9] + if (sc == '+') { + negative = false; + has_sign = true; + } else if (sc == '-') { + negative = true; + has_sign = true; + } else if ((sc >= '0') && (sc <= '9')) { + // ok + } else { + PushError("Sign or 0-9 expected, but got '" + std::to_string(sc) + + "'.\n"); + return false; + } + + ss << sc; + } + + if (negative) { + PushError("Unsigned value expected but got '-' sign."); + return false; + } + + while (!Eof()) { + char c; + if (!Char1(&c)) { + return false; + } + + if ((c >= '0') && (c <= '9')) { + ss << c; + } else { + _sr->seek_from_current(-1); + break; + } + } + + if (has_sign && (ss.str().size() == 1)) { + // sign only + PushError("Integer value expected but got sign character only.\n"); + return false; + } + + if ((ss.str().size() > 1) && (ss.str()[0] == '0')) { + PushError("Zero padded integer value is not allowed.\n"); + return false; + } + + // std::cout << "ReadInt token: " << ss.str() << "\n"; + +#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) + try { + (*value) = uint32_t(std::stoull(ss.str())); + } catch (const std::invalid_argument &e) { + (void)e; + PushError("Not an 64bit unsigned integer literal.\n"); + return false; + } catch (const std::out_of_range &e) { + (void)e; + PushError("64bit unsigned integer value out of range.\n"); + return false; + } + return true; +#else + // use jsteemann/atoi + int retcode = 0; + auto result = jsteemann::atoi( + ss.str().c_str(), ss.str().c_str() + ss.str().size(), retcode); + DCOUT("sz = " << ss.str().size()); + DCOUT("ss = " << ss.str() << ", retcode = " << retcode + << ", result = " << result); + if (retcode == jsteemann::SUCCESS) { + (*value) = result; + return true; + } else if (retcode == jsteemann::INVALID_INPUT) { + PushError("Not an 32bit unsigned integer literal.\n"); + return false; + } else if (retcode == jsteemann::INVALID_NEGATIVE_SIGN) { + PushError("Negative sign `-` specified for uint32 integer.\n"); + return false; + } else if (retcode == jsteemann::VALUE_OVERFLOW) { + PushError("Integer value overflows.\n"); + return false; + } + + PushError("Invalid integer literal\n"); + return false; +#endif +} + +bool AsciiParser::ReadBasicType(int64_t *value) { + std::stringstream ss; + + // head character + bool has_sign = false; + bool negative = false; + { + char sc; + if (!Char1(&sc)) { + return false; + } + _curr_cursor.col++; + + // sign or [0-9] + if (sc == '+') { + negative = false; + has_sign = true; + } else if (sc == '-') { + negative = true; + has_sign = true; + } else if ((sc >= '0') && (sc <= '9')) { + // ok + } else { + PushError("Sign or 0-9 expected, but got '" + std::to_string(sc) + + "'.\n"); + return false; + } + + ss << sc; + } + + if (negative) { + PushError("Unsigned value expected but got '-' sign."); + return false; + } + + while (!Eof()) { + char c; + if (!Char1(&c)) { + return false; + } + + if ((c >= '0') && (c <= '9')) { + ss << c; + } else { + _sr->seek_from_current(-1); + break; + } + } + + if (has_sign && (ss.str().size() == 1)) { + // sign only + PushError("Integer value expected but got sign character only.\n"); + return false; + } + + if ((ss.str().size() > 1) && (ss.str()[0] == '0')) { + PushError("Zero padded integer value is not allowed.\n"); + return false; + } + + // std::cout << "ReadInt token: " << ss.str() << "\n"; + + // TODO(syoyo): Use ryu parse. +#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) + try { + (*value) = std::stoull(ss.str()); + } catch (const std::invalid_argument &e) { + (void)e; + PushError("Not an 64bit unsigned integer literal.\n"); + return false; + } catch (const std::out_of_range &e) { + (void)e; + PushError("64bit unsigned integer value out of range.\n"); + return false; + } + + return true; +#else + // use jsteemann/atoi + int retcode; + auto result = jsteemann::atoi( + ss.str().c_str(), ss.str().c_str() + ss.str().size(), retcode); + if (retcode == jsteemann::SUCCESS) { + (*value) = result; + return true; + } else if (retcode == jsteemann::INVALID_INPUT) { + PushError("Not an 32bit unsigned integer literal.\n"); + return false; + } else if (retcode == jsteemann::INVALID_NEGATIVE_SIGN) { + PushError("Negative sign `-` specified for uint32 integer.\n"); + return false; + } else if (retcode == jsteemann::VALUE_OVERFLOW) { + PushError("Integer value overflows.\n"); + return false; + } + + PushError("Invalid integer literal\n"); + return false; +#endif + + // std::cout << "read int ok\n"; +} + +bool AsciiParser::ReadBasicType(uint64_t *value) { + std::stringstream ss; + + // head character + bool has_sign = false; + bool negative = false; + { + char sc; + if (!Char1(&sc)) { + return false; + } + _curr_cursor.col++; + + // sign or [0-9] + if (sc == '+') { + negative = false; + has_sign = true; + } else if (sc == '-') { + negative = true; + has_sign = true; + } else if ((sc >= '0') && (sc <= '9')) { + // ok + } else { + PushError("Sign or 0-9 expected, but got '" + std::to_string(sc) + + "'.\n"); + return false; + } + + ss << sc; + } + + if (negative) { + PushError("Unsigned value expected but got '-' sign."); + return false; + } + + while (!Eof()) { + char c; + if (!Char1(&c)) { + return false; + } + + if ((c >= '0') && (c <= '9')) { + ss << c; + } else { + _sr->seek_from_current(-1); + break; + } + } + + if (has_sign && (ss.str().size() == 1)) { + // sign only + PushError("Integer value expected but got sign character only.\n"); + return false; + } + + if ((ss.str().size() > 1) && (ss.str()[0] == '0')) { + PushError("Zero padded integer value is not allowed.\n"); + return false; + } + + // std::cout << "ReadInt token: " << ss.str() << "\n"; + + // TODO(syoyo): Use ryu parse. +#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) + try { + (*value) = std::stoull(ss.str()); + } catch (const std::invalid_argument &e) { + (void)e; + PushError("Not an 64bit unsigned integer literal.\n"); + return false; + } catch (const std::out_of_range &e) { + (void)e; + PushError("64bit unsigned integer value out of range.\n"); + return false; + } + + return true; +#else + // use jsteemann/atoi + int retcode; + auto result = jsteemann::atoi( + ss.str().c_str(), ss.str().c_str() + ss.str().size(), retcode); + if (retcode == jsteemann::SUCCESS) { + (*value) = result; + return true; + } else if (retcode == jsteemann::INVALID_INPUT) { + PushError("Not an 32bit unsigned integer literal.\n"); + return false; + } else if (retcode == jsteemann::INVALID_NEGATIVE_SIGN) { + PushError("Negative sign `-` specified for uint32 integer.\n"); + return false; + } else if (retcode == jsteemann::VALUE_OVERFLOW) { + PushError("Integer value overflows.\n"); + return false; + } + + PushError("Invalid integer literal\n"); + return false; +#endif + + // std::cout << "read int ok\n"; +} + +bool AsciiParser::ReadBasicType(value::float2 *value) { + return ParseBasicTypeTuple(value); +} + +bool AsciiParser::ReadBasicType(value::float3 *value) { + return ParseBasicTypeTuple(value); +} + +bool AsciiParser::ReadBasicType(value::point3f *value) { + value::float3 v; + if (ParseBasicTypeTuple(&v)) { + value->x = v[0]; + value->y = v[1]; + value->z = v[2]; + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(value::normal3f *value) { + value::float3 v; + if (ParseBasicTypeTuple(&v)) { + value->x = v[0]; + value->y = v[1]; + value->z = v[2]; + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(value::vector3h *value) { + value::float3 v; + if (ParseBasicTypeTuple(&v)) { + value->x = value::float_to_half_full(v[0]); + value->y = value::float_to_half_full(v[1]); + value->z = value::float_to_half_full(v[2]); + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(value::vector3f *value) { + value::float3 v; + if (ParseBasicTypeTuple(&v)) { + value->x = v[0]; + value->y = v[1]; + value->z = v[2]; + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(value::vector3d *value) { + value::double3 v; + if (ParseBasicTypeTuple(&v)) { + value->x = v[0]; + value->y = v[1]; + value->z = v[2]; + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(value::float4 *value) { + return ParseBasicTypeTuple(value); +} + +bool AsciiParser::ReadBasicType(value::double2 *value) { + return ParseBasicTypeTuple(value); +} + +bool AsciiParser::ReadBasicType(value::double3 *value) { + return ParseBasicTypeTuple(value); +} + +bool AsciiParser::ReadBasicType(value::point3h *value) { + // parse as float3 + value::float3 v; + if (ParseBasicTypeTuple(&v)) { + value->x = value::float_to_half_full(v[0]); + value->y = value::float_to_half_full(v[1]); + value->z = value::float_to_half_full(v[2]); + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(value::point3d *value) { + value::double3 v; + if (ParseBasicTypeTuple(&v)) { + value->x = v[0]; + value->y = v[1]; + value->z = v[2]; + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(value::color3h *value) { + // parse as float3 + value::float3 v; + if (ParseBasicTypeTuple(&v)) { + value->r = value::float_to_half_full(v[0]); + value->g = value::float_to_half_full(v[1]); + value->b = value::float_to_half_full(v[2]); + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(value::color3f *value) { + value::float3 v; + if (ParseBasicTypeTuple(&v)) { + value->r = v[0]; + value->g = v[1]; + value->b = v[2]; + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(value::color3d *value) { + value::double3 v; + if (ParseBasicTypeTuple(&v)) { + value->r = v[0]; + value->g = v[1]; + value->b = v[2]; + return true; + } + return false; +} + +#if 0 +template <> +bool AsciiParser::ReadBasicType(value::point4h *value) { + // parse as float4 + value::float4 v; + if (ParseBasicTypeTuple(&v)) { + value->x = value::float_to_half_full(v[0]); + value->y = value::float_to_half_full(v[1]); + value->z = value::float_to_half_full(v[2]); + value->w = value::float_to_half_full(v[3]); + return true; + } + return false; +} +#endif + +bool AsciiParser::ReadBasicType(value::color4h *value) { + // parse as float4 + value::float4 v; + if (ParseBasicTypeTuple(&v)) { + value->r = value::float_to_half_full(v[0]); + value->g = value::float_to_half_full(v[1]); + value->b = value::float_to_half_full(v[2]); + value->a = value::float_to_half_full(v[3]); + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(value::color4f *value) { + value::float4 v; + if (ParseBasicTypeTuple(&v)) { + value->r = v[0]; + value->g = v[1]; + value->b = v[2]; + value->a = v[3]; + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(value::color4d *value) { + value::double4 v; + if (ParseBasicTypeTuple(&v)) { + value->r = v[0]; + value->g = v[1]; + value->b = v[2]; + value->a = v[3]; + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(value::normal3h *value) { + // parse as float3 + value::float3 v; + if (ParseBasicTypeTuple(&v)) { + value->x = value::float_to_half_full(v[0]); + value->y = value::float_to_half_full(v[1]); + value->z = value::float_to_half_full(v[2]); + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(value::normal3d *value) { + value::double3 v; + if (ParseBasicTypeTuple(&v)) { + value->x = v[0]; + value->y = v[1]; + value->z = v[2]; + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(value::double4 *value) { + return ParseBasicTypeTuple(value); +} + +/// +/// Parses 1 or more occurences of value with basic type 'T', separated by +/// `sep` +/// +template +bool AsciiParser::SepBy1BasicType(const char sep, + std::vector> *result) { + result->clear(); + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + { + nonstd::optional value; + if (!ReadBasicType(&value)) { + PushError("Not starting with the value of requested type.\n"); + return false; + } + + result->push_back(value); + } + + while (!Eof()) { + // sep + if (!SkipWhitespaceAndNewline()) { + return false; + } + + char c; + if (!Char1(&c)) { + return false; + } + + if (c != sep) { + // end + _sr->seek_from_current(-1); // unwind single char + break; + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + nonstd::optional value; + if (!ReadBasicType(&value)) { + break; + } + + result->push_back(value); + } + + if (result->empty()) { + PushError("Empty array.\n"); + return false; + } + + return true; +} + +/// +/// Parses 1 or more occurences of value with basic type 'T', separated by +/// `sep` +/// +template +bool AsciiParser::SepBy1BasicType(const char sep, std::vector *result) { + result->clear(); + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + { + T value; + if (!ReadBasicType(&value)) { + PushError("Not starting with the value of requested type.\n"); + return false; + } + + result->push_back(value); + } + + while (!Eof()) { + // sep + if (!SkipWhitespaceAndNewline()) { + return false; + } + + char c; + if (!Char1(&c)) { + return false; + } + + if (c != sep) { + // end + _sr->seek_from_current(-1); // unwind single char + break; + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + T value; + if (!ReadBasicType(&value)) { + break; + } + + result->push_back(value); + } + + if (result->empty()) { + PushError("Empty array.\n"); + return false; + } + + return true; +} + +/// +/// Parses 1 or more occurences of value with basic type 'T', separated by +/// `sep`. +/// Allow `sep` character in the last item of the array. +/// +template +bool AsciiParser::SepBy1BasicType(const char sep, const char end_symbol, std::vector *result) { + result->clear(); + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + { + T value; + if (!ReadBasicType(&value)) { + PushError("Not starting with the value of requested type.\n"); + return false; + } + + result->push_back(value); + } + + while (!Eof()) { + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + // sep + char c; + if (!Char1(&c)) { + return false; + } + + if (c == sep) { + // Look next token + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + char nc; + if (!LookChar1(&nc)) { + return false; + } + + if (nc == end_symbol) { + // end + break; + } + } + + if (c != sep) { + // end + _sr->seek_from_current(-1); // unwind single char + break; + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + T value; + if (!ReadBasicType(&value)) { + break; + } + + result->push_back(value); + + + } + + if (result->empty()) { + PushError("Empty array.\n"); + return false; + } + + return true; +} + +/// +/// Parses 1 or more occurences of value with tuple type 'T', separated by +/// `sep` +/// +template +bool AsciiParser::SepBy1TupleType( + const char sep, std::vector>> *result) { + result->clear(); + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + if (MaybeNone()) { + result->push_back(nonstd::nullopt); + } else { + std::array value; + if (!ParseBasicTypeTuple(&value)) { + PushError("Not starting with the tuple value of requested type.\n"); + return false; + } + + result->push_back(value); + } + + while (!Eof()) { + if (!SkipWhitespaceAndNewline()) { + return false; + } + + char c; + if (!Char1(&c)) { + return false; + } + + if (c != sep) { + // end + _sr->seek_from_current(-1); // unwind single char + break; + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + if (MaybeNone()) { + result->push_back(nonstd::nullopt); + } else { + std::array value; + if (!ParseBasicTypeTuple(&value)) { + break; + } + result->push_back(value); + } + } + + if (result->empty()) { + PushError("Empty array.\n"); + return false; + } + + return true; +} + +/// +/// Parses 1 or more occurences of value with tuple type 'T', separated by +/// `sep` +/// +template +bool AsciiParser::SepBy1TupleType(const char sep, + std::vector> *result) { + result->clear(); + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + { + std::array value; + if (!ParseBasicTypeTuple(&value)) { + PushError("Not starting with the tuple value of requested type.\n"); + return false; + } + + result->push_back(value); + } + + while (!Eof()) { + if (!SkipWhitespaceAndNewline()) { + return false; + } + + char c; + if (!Char1(&c)) { + return false; + } + + if (c != sep) { + // end + _sr->seek_from_current(-1); // unwind single char + break; + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + std::array value; + if (!ParseBasicTypeTuple(&value)) { + break; + } + + result->push_back(value); + } + + if (result->empty()) { + PushError("Empty array.\n"); + return false; + } + + return true; +} + +/// +/// Parse '[', Sep1By(','), ']' +/// +template +bool AsciiParser::ParseBasicTypeArray( + std::vector> *result) { + if (!Expect('[')) { + return false; + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + // Empty array? + { + char c; + if (!Char1(&c)) { + return false; + } + + if (c == ']') { + result->clear(); + return true; + } + + Rewind(1); + } + + if (!SepBy1BasicType(',', ']', result)) { + return false; + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + if (!Expect(']')) { + return false; + } + + return true; +} + +/// +/// Parse '[', Sep1By(','), ']' +/// +template +bool AsciiParser::ParseBasicTypeArray(std::vector *result) { + if (!Expect('[')) { + return false; + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + // Empty array? + { + char c; + if (!Char1(&c)) { + return false; + } + + if (c == ']') { + result->clear(); + return true; + } + + Rewind(1); + } + + if (!SepBy1BasicType(',', ']', result)) { + return false; + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + if (!Expect(']')) { + return false; + } + return true; +} + +/// +/// Parses 1 or more occurences of asset references, separated by +/// `sep` +/// TODO: Parse LayerOffset: e.g. `(offset = 10; scale = 2)` +/// +template <> +bool AsciiParser::SepBy1BasicType(const char sep, + const char end_symbol, + std::vector *result) { + result->clear(); + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + { + Reference ref; + bool triple_deliminated{false}; + + if (!ParseReference(&ref, &triple_deliminated)) { + PushError("Failed to parse Reference.\n"); + return false; + } + + (void)triple_deliminated; + + result->push_back(ref); + } + + while (!Eof()) { + // sep + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + char c; + if (!Char1(&c)) { + return false; + } + + if (c == sep) { + // Look next token + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + char nc; + if (!LookChar1(&nc)) { + return false; + } + + if (nc == end_symbol) { + // end + break; + } + } + + if (c != sep) { + // end + _sr->seek_from_current(-1); // unwind single char + break; + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + Reference ref; + bool triple_deliminated{false}; + if (!ParseReference(&ref, &triple_deliminated)) { + break; + } + + (void)triple_deliminated; + result->push_back(ref); + } + + if (result->empty()) { + PushError("Empty array.\n"); + return false; + } + + return true; +} + +bool AsciiParser::ParsePurpose(Purpose *result) { + if (!result) { + return false; + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + std::string str; + if (!ReadIdentifier(&str)) { + return false; + } + + if (str == "\"default\"") { + (*result) = Purpose::Default; + } else if (str == "\"render\"") { + (*result) = Purpose::Render; + } else if (str == "\"proxy\"") { + (*result) = Purpose::Proxy; + } else if (str == "\"guide\"") { + (*result) = Purpose::Guide; + } else { + PUSH_ERROR_AND_RETURN_TAG(kAscii, "Invalid purpose value: " + str + "\n"); + } + + return true; +} + +template +bool AsciiParser::ParseBasicTypeTuple(std::array *result) { + if (!Expect('(')) { + return false; + } + + std::vector values; + if (!SepBy1BasicType(',', &values)) { + return false; + } + + if (!Expect(')')) { + return false; + } + + if (values.size() != N) { + std::string msg = "The number of tuple elements must be " + + std::to_string(N) + ", but got " + + std::to_string(values.size()) + "\n"; + PushError(msg); + return false; + } + + for (size_t i = 0; i < N; i++) { + (*result)[i] = values[i]; + } + + return true; +} + +template +bool AsciiParser::ParseBasicTypeTuple( + nonstd::optional> *result) { + if (MaybeNone()) { + (*result) = nonstd::nullopt; + return true; + } + + if (!Expect('(')) { + return false; + } + + std::vector values; + if (!SepBy1BasicType(',', &values)) { + return false; + } + + if (!Expect(')')) { + return false; + } + + if (values.size() != N) { + PUSH_ERROR_AND_RETURN("The number of tuple elements must be " + + std::to_string(N) + ", but got " + + std::to_string(values.size())); + } + + std::array ret; + for (size_t i = 0; i < N; i++) { + ret[i] = values[i]; + } + + (*result) = ret; + + return true; +} + +/// +/// Parse array of asset references +/// Allow non-list version +/// +template<> +bool AsciiParser::ParseBasicTypeArray(std::vector *result) { + if (!SkipWhitespace()) { + return false; + } + + char c; + if (!Char1(&c)) { + return false; + } + + if (c != '[') { + Rewind(1); + + // Guess non-list version + Reference ref; + bool triple_deliminated{false}; + if (!ParseReference(&ref, &triple_deliminated)) { + return false; + } + + (void)triple_deliminated; + result->clear(); + result->push_back(ref); + + } else { + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + // Empty array? + { + char ce; + if (!Char1(&ce)) { + return false; + } + + if (ce == ']') { + result->clear(); + return true; + } + + Rewind(1); + } + + + if (!SepBy1BasicType(',', ']', result)) { + return false; + } + DCOUT("parsed ref array"); + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + if (!Expect(']')) { + return false; + } + + } + + return true; +} + +/// +/// Parse array of asset payload +/// Allow non-list version +/// +template<> +bool AsciiParser::ParseBasicTypeArray(std::vector *result) { + if (!SkipWhitespace()) { + return false; + } + + char c; + if (!Char1(&c)) { + return false; + } + + if (c != '[') { + Rewind(1); + + DCOUT("Guess non-list version"); + // Guess non-list version + Payload pl; + bool triple_deliminated{false}; + if (!ParsePayload(&pl, &triple_deliminated)) { + return false; + } + + (void)triple_deliminated; + result->clear(); + result->push_back(pl); + + } else { + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + // Empty array? + { + char ce; + if (!Char1(&ce)) { + return false; + } + + if (ce == ']') { + result->clear(); + return true; + } + + Rewind(1); + } + + if (!SepBy1BasicType(',', ']', result)) { + return false; + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + if (!Expect(']')) { + return false; + } + } + + return true; +} + + +/// +/// Parse PathVector +/// +template<> +bool AsciiParser::ParseBasicTypeArray(std::vector *result) { + if (!SkipWhitespace()) { + return false; + } + + if (!Expect('[')) { + return false; + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + // Empty array? + { + char c; + if (!Char1(&c)) { + return false; + } + + if (c == ']') { + result->clear(); + return true; + } + + Rewind(1); + } + + if (!SepBy1BasicType(',', ']', result)) { + return false; + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + if (!Expect(']')) { + return false; + } + + return true; +} + +template +bool AsciiParser::MaybeNonFinite(T *out) { + auto loc = CurrLoc(); + + // "-inf", "inf" or "nan" + std::vector buf(4); + if (!CharN(3, &buf)) { + return false; + } + SeekTo(loc); + + if ((buf[0] == 'i') && (buf[1] == 'n') && (buf[2] == 'f')) { + (*out) = std::numeric_limits::infinity(); + return true; + } + + if ((buf[0] == 'n') && (buf[1] == 'a') && (buf[2] == 'n')) { + (*out) = std::numeric_limits::quiet_NaN(); + return true; + } + + bool ok = CharN(4, &buf); + SeekTo(loc); + + if (ok) { + if ((buf[0] == '-') && (buf[1] == 'i') && (buf[2] == 'n') && + (buf[3] == 'f')) { + (*out) = -std::numeric_limits::infinity(); + return true; + } + + // NOTE: support "-nan"? + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::texcoord2h *value) { + // parse as float2 + value::float2 v; + if (ParseBasicTypeTuple(&v)) { + value->s = value::float_to_half_full(v[0]); + value->t = value::float_to_half_full(v[1]); + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::texcoord2h v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::texcoord2f *value) { + value::float2 v; + if (ParseBasicTypeTuple(&v)) { + value->s = v[0]; + value->t = v[1]; + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::texcoord2f v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::texcoord2d *value) { + value::double2 v; + if (ParseBasicTypeTuple(&v)) { + value->s = v[0]; + value->t = v[1]; + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::texcoord2d v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::texcoord3h *value) { + // parse as float3 + value::float3 v; + if (ParseBasicTypeTuple(&v)) { + value->s = value::float_to_half_full(v[0]); + value->t = value::float_to_half_full(v[1]); + value->r = value::float_to_half_full(v[2]); + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::texcoord3h v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::texcoord3f *value) { + + value::float3 v; + if (ParseBasicTypeTuple(&v)) { + value->s = v[0]; + value->t = v[1]; + value->r = v[2]; + return true; + } + + return true; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::texcoord3f v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::texcoord3d *value) { + if (!Expect('(')) { + return false; + } + + std::vector values; + if (!SepBy1BasicType(',', &values)) { + return false; + } + + if (!Expect(')')) { + return false; + } + + if (values.size() != 3) { + std::string msg = "The number of tuple elements must be " + + std::to_string(3) + ", but got " + + std::to_string(values.size()) + "\n"; + PUSH_ERROR_AND_RETURN(msg); + } + + value->s = values[0]; + value->t = values[1]; + value->r = values[2]; + + return true; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::texcoord3d v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::float2 v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::float3 v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::float4 v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::double2 v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::double3 v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::double4 v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::point3f v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::point3d v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::normal3f v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::normal3d v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::vector3f v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::vector3d v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::color3f v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::color4f v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::color3d v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::color4d v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + int v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + uint32_t v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + int64_t v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + uint64_t v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(float *value) { + // -inf, inf, nan + { + float v; + if (MaybeNonFinite(&v)) { + (*value) = v; + return true; + } + } + + std::string value_str; + if (!LexFloat(&value_str)) { + PUSH_ERROR_AND_RETURN("Failed to lex floating value literal."); + } + + auto flt = ParseFloat(value_str); + if (flt) { + (*value) = flt.value(); + } else { + PUSH_ERROR_AND_RETURN("Failed to parse floating value."); + } + + return true; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + float v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(double *value) { + // -inf, inf, nan + { + double v; + if (MaybeNonFinite(&v)) { + (*value) = v; + return true; + } + } + + std::string value_str; + if (!LexFloat(&value_str)) { + PUSH_ERROR_AND_RETURN("Failed to lex floating value literal."); + } + + auto flt = ParseDouble(value_str); + if (!flt) { + PUSH_ERROR_AND_RETURN("Failed to parse floating value."); + } else { + (*value) = flt.value(); + } + + return true; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + double v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::half *value) { + // Parse as float + float v; + if (!ReadBasicType(&v)) { + return false; + } + + (*value) = value::float_to_half_full(v); + return true; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::half v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::half2 *value) { + // Parse as float + value::float2 v; + if (!ReadBasicType(&v)) { + return false; + } + + (*value)[0] = value::float_to_half_full(v[0]); + (*value)[1] = value::float_to_half_full(v[1]); + return true; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::half2 v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::half3 *value) { + // Parse as float + value::float3 v; + if (!ReadBasicType(&v)) { + return false; + } + + (*value)[0] = value::float_to_half_full(v[0]); + (*value)[1] = value::float_to_half_full(v[1]); + (*value)[2] = value::float_to_half_full(v[2]); + return true; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::half3 v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::half4 *value) { + // Parse as float + value::float4 v; + if (!ReadBasicType(&v)) { + return false; + } + + (*value)[0] = value::float_to_half_full(v[0]); + (*value)[1] = value::float_to_half_full(v[1]); + (*value)[2] = value::float_to_half_full(v[2]); + (*value)[3] = value::float_to_half_full(v[3]); + return true; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::half4 v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::quath *value) { + value::half4 v; + if (ReadBasicType(&v)) { + value->real = v[0]; + value->imag[0] = v[1]; + value->imag[1] = v[2]; + value->imag[2] = v[3]; + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::quath v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::quatf *value) { + value::float4 v; + if (ReadBasicType(&v)) { + value->real = v[0]; + value->imag[0] = v[1]; + value->imag[1] = v[2]; + value->imag[2] = v[3]; + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::quatf v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::quatd *value) { + value::double4 v; + if (ReadBasicType(&v)) { + value->real = v[0]; + value->imag[0] = v[1]; + value->imag[1] = v[2]; + value->imag[2] = v[3]; + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::quatd v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(value::AssetPath *value) { + bool triple_deliminated; + if (ParseAssetIdentifier(value, &triple_deliminated)) { + return true; + } + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + value::AssetPath v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(Reference *value) { + bool triple_deliminated; + if (ParseReference(value, &triple_deliminated)) { + return true; + } + (void)triple_deliminated; + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + Reference v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +bool AsciiParser::ReadBasicType(Payload *value) { + bool triple_deliminated; + if (ParsePayload(value, &triple_deliminated)) { + return true; + } + (void)triple_deliminated; + + return false; +} + +bool AsciiParser::ReadBasicType(nonstd::optional *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + Payload v; + if (ReadBasicType(&v)) { + (*value) = v; + return true; + } + + return false; +} + +// 1D array +template +bool AsciiParser::ReadBasicType(std::vector *value) { + return ParseBasicTypeArray(value); +} + +template +bool AsciiParser::ReadBasicType(nonstd::optional> *value) { + if (MaybeNone()) { + (*value) = nonstd::nullopt; + return true; + } + + std::vector v; + if (ParseBasicTypeArray(&v)) { + (*value) = v; + return true; + } + + return false; +} + +// -- end basic + +// +// Explicit template instanciations +// + +#if 0 +//template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +#endif + +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +//template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +//template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +template bool AsciiParser::ParseBasicTypeArray(std::vector *result); + + +} // namespace ascii +} // namespace tinyusdz + +#else // TINYUSDZ_DISABLE_MODULE_USDA_READER + +#endif // TINYUSDZ_DISABLE_MODULE_USDA_READER diff --git a/contrib/tinyusdz/tinyusdz_repo/src/ascii-parser-timesamples-array.cc b/contrib/tinyusdz/tinyusdz_repo/src/ascii-parser-timesamples-array.cc new file mode 100644 index 000000000..ebe113f8c --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/ascii-parser-timesamples-array.cc @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2021 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +// To deal with too many sections in generated .obj error(happens in MinGW and MSVC) +// Split ParseTimeSamples to two .cc files. +// +// TODO +// - [x] Rewrite code with less C++ template code. + +#include +#ifdef _MSC_VER +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__wasi__) +#else +#include +#include +#endif +#include + +#include "ascii-parser.hh" +#include "str-util.hh" +#include "tiny-format.hh" + +// +#if !defined(TINYUSDZ_DISABLE_MODULE_USDA_READER) + +// + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +// external + +//#include "external/fast_float/include/fast_float/fast_float.h" +//#include "external/jsteemann/atoi.h" +//#include "external/simple_match/include/simple_match/simple_match.hpp" +#include "nonstd/expected.hpp" + +// + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// + +// Tentative +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wunused-parameter" +#endif + +#include "common-macros.inc" +#include "io-util.hh" +#include "pprinter.hh" +#include "prim-types.hh" +#include "str-util.hh" +#include "stream-reader.hh" +#include "tinyusdz.hh" +#include "value-pprint.hh" +#include "value-types.hh" + +namespace tinyusdz { + +namespace ascii { + +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); + +// +// -- impl ParseTimeSampleData +// + +bool AsciiParser::ParseTimeSampleValueOfArrayType(const uint32_t type_id, value::Value *result) { + + if (!result) { + return false; + } + + if (MaybeNone()) { + (*result) = value::ValueBlock(); + return true; + } + + value::Value val; + +#define PARSE_TYPE(__tyid, __type) \ + if (__tyid == value::TypeTraits<__type>::type_id()) { \ + std::vector<__type> typed_val; \ + if (!ParseBasicTypeArray(&typed_val)) { \ + PUSH_ERROR_AND_RETURN("Failed to parse value with requested type `" + value::GetTypeName(__tyid) + "[]`"); \ + } \ + val = value::Value(typed_val); \ + } else + + // NOTE: `string` does not support multi-line string. + PARSE_TYPE(type_id, value::AssetPath) + PARSE_TYPE(type_id, value::token) + PARSE_TYPE(type_id, std::string) + PARSE_TYPE(type_id, float) + PARSE_TYPE(type_id, int32_t) + PARSE_TYPE(type_id, uint32_t) + PARSE_TYPE(type_id, int64_t) + PARSE_TYPE(type_id, uint64_t) + PARSE_TYPE(type_id, value::half) + PARSE_TYPE(type_id, value::half2) + PARSE_TYPE(type_id, value::half3) + PARSE_TYPE(type_id, value::half4) + PARSE_TYPE(type_id, float) + PARSE_TYPE(type_id, value::float2) + PARSE_TYPE(type_id, value::float3) + PARSE_TYPE(type_id, value::float4) + PARSE_TYPE(type_id, double) + PARSE_TYPE(type_id, value::double2) + PARSE_TYPE(type_id, value::double3) + PARSE_TYPE(type_id, value::double4) + PARSE_TYPE(type_id, value::quath) + PARSE_TYPE(type_id, value::quatf) + PARSE_TYPE(type_id, value::quatd) + PARSE_TYPE(type_id, value::color3f) + PARSE_TYPE(type_id, value::color4f) + PARSE_TYPE(type_id, value::color3d) + PARSE_TYPE(type_id, value::color4d) + PARSE_TYPE(type_id, value::vector3f) + PARSE_TYPE(type_id, value::normal3f) + PARSE_TYPE(type_id, value::point3f) + PARSE_TYPE(type_id, value::texcoord2f) + PARSE_TYPE(type_id, value::texcoord3f) + PARSE_TYPE(type_id, value::matrix2f) + PARSE_TYPE(type_id, value::matrix3f) + PARSE_TYPE(type_id, value::matrix4f) + PARSE_TYPE(type_id, value::matrix2d) + PARSE_TYPE(type_id, value::matrix3d) + PARSE_TYPE(type_id, value::matrix4d) { + PUSH_ERROR_AND_RETURN(" : TODO: timeSamples type " + value::GetTypeName(type_id)); + } + +#undef PARSE_TYPE + + (*result) = val; + + return true; + +} + +// `type_name` does not contain "[]" +bool AsciiParser::ParseTimeSampleValueOfArrayType(const std::string &type_name, value::Value *result) { + nonstd::optional type_id = value::TryGetTypeId(type_name); + if (!type_id) { + PUSH_ERROR_AND_RETURN("Unsupported/invalid type name: " + type_name); + } + + return ParseTimeSampleValueOfArrayType(type_id.value(), result); +} + +bool AsciiParser::ParseTimeSamplesOfArray(const std::string &type_name, + value::TimeSamples *ts_out) { + + value::TimeSamples ts; + + if (!Expect('{')) { + return false; + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + while (!Eof()) { + char c; + if (!Char1(&c)) { + return false; + } + + if (c == '}') { + break; + } + + Rewind(1); + + double timeVal; + // -inf, inf and nan are handled. + if (!ReadBasicType(&timeVal)) { + PushError("Parse time value failed."); + return false; + } + + if (!SkipWhitespace()) { + return false; + } + + if (!Expect(':')) { + return false; + } + + if (!SkipWhitespace()) { + return false; + } + + value::Value value; + if (!ParseTimeSampleValueOfArrayType(type_name, &value)) { // could be None(ValueBlock) + return false; + } + + // The last element may have separator ',' + { + // Semicolon ';' is not allowed as a separator for timeSamples array + // values. + if (!SkipWhitespace()) { + return false; + } + + char sep{}; + if (!Char1(&sep)) { + return false; + } + + DCOUT("sep = " << sep); + if (sep == '}') { + // End of item + ts.add_sample(timeVal, value); + break; + } else if (sep == ',') { + // ok + } else { + Rewind(1); + + // Look ahead Newline + '}' + auto loc = CurrLoc(); + + if (SkipWhitespaceAndNewline()) { + char nc; + if (!Char1(&nc)) { + return false; + } + + if (nc == '}') { + // End of item + ts.add_sample(timeVal, value); + break; + } + } + + // Rewind and continue parsing. + SeekTo(loc); + } + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + ts.add_sample(timeVal, value); + } + + DCOUT("Parse TimeSamples success. # of items = " << ts.size()); + + if (ts_out) { + (*ts_out) = std::move(ts); + } + + return true; +} + +} // namespace ascii +} // namespace tinyusdz + +#else // TINYUSDZ_DISABLE_MODULE_USDA_READER + +#endif // TINYUSDZ_DISABLE_MODULE_USDA_READER diff --git a/contrib/tinyusdz/tinyusdz_repo/src/ascii-parser-timesamples.cc b/contrib/tinyusdz/tinyusdz_repo/src/ascii-parser-timesamples.cc new file mode 100644 index 000000000..b5d896012 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/ascii-parser-timesamples.cc @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2021 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +// To deal with too many sections in generated .obj error(happens in MinGW and MSVC) +// Split ParseTimeSamples to two .cc files. +// +// TODO +// - [x] Rewrite code with less C++ template code. + +#include +#ifdef _MSC_VER +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__wasi__) +#else +#include +#include +#endif +#include + +// +#if !defined(TINYUSDZ_DISABLE_MODULE_USDA_READER) + +// + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +// external + +//#include "external/fast_float/include/fast_float/fast_float.h" +//#include "external/jsteemann/atoi.h" +//#include "external/simple_match/include/simple_match/simple_match.hpp" +#include "nonstd/expected.hpp" + +// + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// + +// Tentative +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wunused-parameter" +#endif + +#include "ascii-parser.hh" +#include "str-util.hh" +#include "tiny-format.hh" +// +#include "io-util.hh" +#include "pprinter.hh" +#include "prim-types.hh" +#include "str-util.hh" +#include "stream-reader.hh" +#include "tinyusdz.hh" +#include "value-pprint.hh" +#include "value-types.hh" +// +#include "common-macros.inc" + +namespace tinyusdz { + +namespace ascii { + +bool AsciiParser::ParseTimeSampleValue(const uint32_t type_id, value::Value *result) { + + if (!result) { + return false; + } + + if (MaybeNone()) { + (*result) = value::ValueBlock(); + return true; + } + + value::Value val; + +#define PARSE_TYPE(__tyid, __type) \ + if (__tyid == value::TypeTraits<__type>::type_id()) { \ + __type typed_val; \ + if (!ReadBasicType(&typed_val)) { \ + PUSH_ERROR_AND_RETURN("Failed to parse value with requested type `" + value::GetTypeName(__tyid) + "`"); \ + } \ + val = value::Value(typed_val); \ + } else + + // NOTE: `string` does not support multi-line string. + PARSE_TYPE(type_id, value::AssetPath) + PARSE_TYPE(type_id, value::token) + PARSE_TYPE(type_id, std::string) + PARSE_TYPE(type_id, float) + PARSE_TYPE(type_id, int32_t) + PARSE_TYPE(type_id, value::int2) + PARSE_TYPE(type_id, value::int3) + PARSE_TYPE(type_id, value::int4) + PARSE_TYPE(type_id, uint32_t) + PARSE_TYPE(type_id, int64_t) + PARSE_TYPE(type_id, uint64_t) + PARSE_TYPE(type_id, value::half) + PARSE_TYPE(type_id, value::half2) + PARSE_TYPE(type_id, value::half3) + PARSE_TYPE(type_id, value::half4) + PARSE_TYPE(type_id, float) + PARSE_TYPE(type_id, value::float2) + PARSE_TYPE(type_id, value::float3) + PARSE_TYPE(type_id, value::float4) + PARSE_TYPE(type_id, double) + PARSE_TYPE(type_id, value::double2) + PARSE_TYPE(type_id, value::double3) + PARSE_TYPE(type_id, value::double4) + PARSE_TYPE(type_id, value::quath) + PARSE_TYPE(type_id, value::quatf) + PARSE_TYPE(type_id, value::quatd) + PARSE_TYPE(type_id, value::color3f) + PARSE_TYPE(type_id, value::color4f) + PARSE_TYPE(type_id, value::color3d) + PARSE_TYPE(type_id, value::color4d) + PARSE_TYPE(type_id, value::vector3f) + PARSE_TYPE(type_id, value::normal3f) + PARSE_TYPE(type_id, value::point3f) + PARSE_TYPE(type_id, value::texcoord2f) + PARSE_TYPE(type_id, value::texcoord3f) + PARSE_TYPE(type_id, value::matrix2f) + PARSE_TYPE(type_id, value::matrix3f) + PARSE_TYPE(type_id, value::matrix4f) + PARSE_TYPE(type_id, value::matrix2d) + PARSE_TYPE(type_id, value::matrix3d) + PARSE_TYPE(type_id, value::matrix4d) { + PUSH_ERROR_AND_RETURN(" : TODO: timeSamples type " + value::GetTypeName(type_id)); + } + +#undef PARSE_TYPE + + (*result) = val; + + return true; +} + +bool AsciiParser::ParseTimeSampleValue(const std::string &type_name, value::Value *result) { + + nonstd::optional type_id = value::TryGetTypeId(type_name); + + if (!type_id) { + PUSH_ERROR_AND_RETURN("Unsupported/invalid timeSamples type " + type_name); + } + + return ParseTimeSampleValue(type_id.value(), result); +} + + +bool AsciiParser::ParseTimeSamples(const std::string &type_name, + value::TimeSamples *ts_out) { + + value::TimeSamples ts; + + if (!Expect('{')) { + return false; + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + while (!Eof()) { + char c; + if (!Char1(&c)) { + return false; + } + + if (c == '}') { + break; + } + + Rewind(1); + + double timeVal; + // -inf, inf and nan are handled. + if (!ReadBasicType(&timeVal)) { + PushError("Parse time value failed."); + return false; + } + + if (!SkipWhitespace()) { + return false; + } + + if (!Expect(':')) { + return false; + } + + if (!SkipWhitespace()) { + return false; + } + + value::Value value; + if (!ParseTimeSampleValue(type_name, &value)) { // could be None(ValueBlock) + return false; + } + + // The last element may have separator ',' + { + // Semicolon ';' is not allowed as a separator for timeSamples array + // values. + if (!SkipWhitespace()) { + return false; + } + + char sep{}; + if (!Char1(&sep)) { + return false; + } + + DCOUT("sep = " << sep); + if (sep == '}') { + // End of item + ts.add_sample(timeVal, value); + break; + } else if (sep == ',') { + // ok + } else { + Rewind(1); + + // Look ahead Newline + '}' + auto loc = CurrLoc(); + + if (SkipWhitespaceAndNewline()) { + char nc; + if (!Char1(&nc)) { + return false; + } + + if (nc == '}') { + // End of item + ts.add_sample(timeVal, value); + break; + } + } + + // Rewind and continue parsing. + SeekTo(loc); + } + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + ts.add_sample(timeVal, value); + } + + DCOUT("Parse TimeSamples success. # of items = " << ts.size()); + + if (ts_out) { + (*ts_out) = std::move(ts); + } + + return true; +} + +} // namespace ascii +} // namespace tinyusdz + +#else // TINYUSDZ_DISABLE_MODULE_USDA_READER + +#endif // TINYUSDZ_DISABLE_MODULE_USDA_READER diff --git a/contrib/tinyusdz/tinyusdz_repo/src/ascii-parser.cc b/contrib/tinyusdz/tinyusdz_repo/src/ascii-parser.cc new file mode 100644 index 000000000..ac79fc273 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/ascii-parser.cc @@ -0,0 +1,4970 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2021 - 2022, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment, Inc. +// +// To reduce compilation time and sections generated in .obj(object file), +// We split implementaion to multiple of .cc for ascii-parser.hh + +#ifdef _MSC_VER +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif +// +#include +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__wasi__) +#else +#include +#include +#endif +#include + +#include "ascii-parser.hh" +#include "str-util.hh" +#include "path-util.hh" +#include "tiny-format.hh" + +// +#if !defined(TINYUSDZ_DISABLE_MODULE_USDA_READER) + +// + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +// external +#include "nonstd/expected.hpp" + +// + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// + +#include "io-util.hh" +#include "pprinter.hh" +#include "prim-types.hh" +#include "str-util.hh" +#include "stream-reader.hh" +#include "tinyusdz.hh" +#include "value-pprint.hh" +#include "value-types.hh" + +#include "common-macros.inc" + +namespace tinyusdz { + +namespace ascii { + +constexpr auto kRel = "rel"; +constexpr auto kTimeSamplesSuffix = ".timeSamples"; +constexpr auto kConnectSuffix = ".connect"; + +constexpr auto kAscii = "[ASCII]"; + +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector> *result); + +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); +extern template bool AsciiParser::ParseBasicTypeArray(std::vector *result); + +static void RegisterStageMetas( + std::map &metas) { + metas.clear(); + metas["doc"] = AsciiParser::VariableDef(value::kString, "doc"); + metas["documentation"] = + AsciiParser::VariableDef(value::kString, "doc"); // alias to 'doc' + + metas["comment"] = AsciiParser::VariableDef(value::kString, "comment"); + + // TODO: both support float and double? + metas["metersPerUnit"] = + AsciiParser::VariableDef(value::kDouble, "metersPerUnit"); + metas["timeCodesPerSecond"] = + AsciiParser::VariableDef(value::kDouble, "timeCodesPerSecond"); + metas["framesPerSecond"] = + AsciiParser::VariableDef(value::kDouble, "framesPerSecond"); + + metas["startTimeCode"] = + AsciiParser::VariableDef(value::kDouble, "startTimeCode"); + metas["endTimeCode"] = + AsciiParser::VariableDef(value::kDouble, "endTimeCode"); + + metas["defaultPrim"] = AsciiParser::VariableDef(value::kToken, "defaultPrim"); + metas["upAxis"] = AsciiParser::VariableDef(value::kToken, "upAxis"); + metas["customLayerData"] = + AsciiParser::VariableDef(value::kDictionary, "customLayerData"); + + // Composition arc. + // Type can be array. i.e. asset, asset[] + metas["subLayers"] = AsciiParser::VariableDef(value::kAssetPath, "subLayers", + /* allow array type */ true); + + // USDZ extension + metas["autoPlay"] = AsciiParser::VariableDef(value::kBool, "autoPlay"); + metas["playbackMode"] = AsciiParser::VariableDef(value::kToken, "playbackMode"); + +} + +static void RegisterPrimMetas( + std::map &metas) { + metas.clear(); + + metas["kind"] = AsciiParser::VariableDef(value::kToken, "kind"); + metas["doc"] = AsciiParser::VariableDef(value::kString, "doc"); + + // + // Composition arcs ----------------------- + // + + // Type can be array. i.e. path, path[] + metas["references"] = AsciiParser::VariableDef("Reference", "references", + /* allow array type */ true); + + // TODO: Use relatioship type? + metas["inherits"] = AsciiParser::VariableDef(value::kPath, "inherits", true); + metas["payload"] = AsciiParser::VariableDef("Payload", "payload", true); + metas["specializes"] = + AsciiParser::VariableDef(value::kPath, "specializes", true); + + // Use `string` + metas["variantSets"] = AsciiParser::VariableDef(value::kString, "variantSets", + /* allow array type */ true); + + // Parse as dict. TODO: Use ParseVariants() + metas["variants"] = AsciiParser::VariableDef(value::kDictionary, "variants"); + + // ------------------------------------------ + + metas["assetInfo"] = + AsciiParser::VariableDef(value::kDictionary, "assetInfo"); + metas["customData"] = + AsciiParser::VariableDef(value::kDictionary, "customData"); + + metas["active"] = AsciiParser::VariableDef(value::kBool, "active"); + metas["hidden"] = AsciiParser::VariableDef(value::kBool, "hidden"); + metas["instanceable"] = AsciiParser::VariableDef(value::kBool, "instanceable"); + + // ListOp + metas["apiSchemas"] = AsciiParser::VariableDef( + value::Add1DArraySuffix(value::kToken), "apiSchemas"); + + // usdShade + // NOTE: items are expected to be all string type. + metas["sdrMetadata"] = AsciiParser::VariableDef(value::kDictionary, "sdrMetadata"); + + metas["clips"] = + AsciiParser::VariableDef(value::kDictionary, "clips"); + + + // USDZ extension + metas["sceneName"] = AsciiParser::VariableDef(value::kString, "sceneName"); + + // Builtin from pxrUSD 23.xx + metas["displayName"] = AsciiParser::VariableDef(value::kString, "displayName"); +} + +static void RegisterPropMetas( + std::map &metas) { + metas.clear(); + + metas["doc"] = AsciiParser::VariableDef(value::kString, "doc"); + metas["active"] = AsciiParser::VariableDef(value::kBool, "active"); + metas["hidden"] = AsciiParser::VariableDef(value::kBool, "hidden"); + metas["customData"] = + AsciiParser::VariableDef(value::kDictionary, "customData"); + + // usdSkel + metas["elementSize"] = AsciiParser::VariableDef(value::kInt, "elementSize"); + + // usdSkel inbetween BlendShape + // use Double in TinyUSDZ. its float type in pxrUSD. + metas["weight"] = AsciiParser::VariableDef(value::kDouble, "weight"); + + // usdShade? + metas["colorSpace"] = AsciiParser::VariableDef(value::kToken, "colorSpace"); + + metas["interpolation"] = AsciiParser::VariableDef(value::kToken, "interpolation"); + + // usdShade + metas["bindMaterialAs"] = AsciiParser::VariableDef(value::kToken, "bindMaterialAs"); + metas["connectability"] = AsciiParser::VariableDef(value::kToken, "connectability"); + metas["renderType"] = AsciiParser::VariableDef(value::kToken, "renderType"); + metas["outputName"] = AsciiParser::VariableDef(value::kToken, "outputName"); + metas["sdrMetadata"] = AsciiParser::VariableDef(value::kDictionary, "sdrMetadata"); + + // Builtin from pxrUSD 23.xx + metas["displayName"] = AsciiParser::VariableDef(value::kString, "displayName"); +} + + +static void RegisterPrimAttrTypes(std::set &d) { + d.clear(); + + d.insert(value::kBool); + + d.insert(value::kInt64); + + d.insert(value::kInt); + d.insert(value::kInt2); + d.insert(value::kInt3); + d.insert(value::kInt4); + + d.insert(value::kUInt64); + + d.insert(value::kUInt); + d.insert(value::kUInt2); + d.insert(value::kUInt3); + d.insert(value::kUInt4); + + d.insert(value::kFloat); + d.insert(value::kFloat2); + d.insert(value::kFloat3); + d.insert(value::kFloat4); + + d.insert(value::kDouble); + d.insert(value::kDouble2); + d.insert(value::kDouble3); + d.insert(value::kDouble4); + + d.insert(value::kHalf); + d.insert(value::kHalf2); + d.insert(value::kHalf3); + d.insert(value::kHalf4); + + d.insert(value::kQuath); + d.insert(value::kQuatf); + d.insert(value::kQuatd); + + d.insert(value::kNormal3f); + d.insert(value::kPoint3f); + d.insert(value::kTexCoord2h); + d.insert(value::kTexCoord3h); + d.insert(value::kTexCoord4h); + d.insert(value::kTexCoord2f); + d.insert(value::kTexCoord3f); + d.insert(value::kTexCoord4f); + d.insert(value::kTexCoord2d); + d.insert(value::kTexCoord3d); + d.insert(value::kTexCoord4d); + d.insert(value::kVector3f); + d.insert(value::kVector4f); + d.insert(value::kVector3d); + d.insert(value::kVector4d); + d.insert(value::kColor3h); + d.insert(value::kColor3f); + d.insert(value::kColor3d); + d.insert(value::kColor4h); + d.insert(value::kColor4f); + d.insert(value::kColor4d); + + d.insert(value::kMatrix2f); + d.insert(value::kMatrix3f); + d.insert(value::kMatrix4f); + + d.insert(value::kMatrix2d); + d.insert(value::kMatrix3d); + d.insert(value::kMatrix4d); + + d.insert(value::kToken); + d.insert(value::kString); + + d.insert(value::kRelationship); + d.insert(value::kAssetPath); + + d.insert(value::kDictionary); + + // variantSet. Require special treatment. + d.insert("variantSet"); + + // TODO: Add more types... +} + +static void RegisterPrimTypes(std::set &d) { + d.insert("Xform"); + d.insert("Sphere"); + d.insert("Cube"); + d.insert("Cone"); + d.insert("Cylinder"); + d.insert("Capsule"); + d.insert("BasisCurves"); + d.insert("Mesh"); + d.insert("Points"); + d.insert("GeomSubset"); + d.insert("Scope"); + d.insert("Material"); + d.insert("NodeGraph"); + d.insert("Shader"); + d.insert("SphereLight"); + d.insert("DomeLight"); + d.insert("DiskLight"); + d.insert("DistantLight"); + d.insert("CylinderLight"); + // d.insert("PortalLight"); + d.insert("Camera"); + d.insert("SkelRoot"); + d.insert("Skeleton"); + d.insert("SkelAnimation"); + d.insert("BlendShape"); + + d.insert("GPrim"); +} + +// TinyUSDZ does not allow user-defined API schema at the moment +// (Primarily for security reason, secondary it requires re-design of Prim +// classes to support user-defined API schema) +static void RegisterAPISchemas(std::set &d) { + d.insert("MaterialBindingAPI"); + d.insert("SkelBindingAPI"); + + // TODO: + // d.insert("PhysicsCollisionAPI"); + // d.insert("PhysicsRigidBodyAPI"); + + // TODO: Support Multi-apply API(`CollectionAPI`) + // d.insert("PhysicsLimitAPI"); + // d.insert("PhysicsDriveAPI"); + // d.insert("CollectionAPI"); +} + +namespace { + +using ReferenceList = std::vector>; + +// https://www.techiedelight.com/trim-string-cpp-remove-leading-trailing-spaces/ +std::string TrimString(const std::string &str) { + const std::string WHITESPACE = " \n\r\t\f\v"; + + // remove leading and trailing whitespaces + std::string s = str; + { + size_t start = s.find_first_not_of(WHITESPACE); + s = (start == std::string::npos) ? "" : s.substr(start); + } + + { + size_t end = s.find_last_not_of(WHITESPACE); + s = (end == std::string::npos) ? "" : s.substr(0, end + 1); + } + + return s; +} + +} // namespace + +inline bool isChar(char c) { return std::isalpha(int(c)); } + +inline bool hasConnect(const std::string &str) { + return endsWith(str, ".connect"); +} + +inline bool hasInputs(const std::string &str) { + return startsWith(str, "inputs:"); +} + +inline bool hasOutputs(const std::string &str) { + return startsWith(str, "outputs:"); +} + +inline bool is_digit(char x) { + return (static_cast((x) - '0') < static_cast(10)); +} + +void AsciiParser::SetBaseDir(const std::string &str) { _base_dir = str; } + +void AsciiParser::SetStream(StreamReader *sr) { _sr = sr; } + +std::string AsciiParser::GetError() { + if (err_stack.empty()) { + return std::string(); + } + + + std::stringstream ss; + while (!err_stack.empty()) { + ErrorDiagnostic diag = err_stack.top(); + + ss << "err_stack[" << (err_stack.size() - 1) << "] USDA source near line " << (diag.cursor.row + 1) << ", col " + << (diag.cursor.col + 1) << ": "; + ss << diag.err; // assume message contains newline. + + err_stack.pop(); + } + + return ss.str(); +} + +std::string AsciiParser::GetWarning() { + if (warn_stack.empty()) { + return std::string(); + } + + std::stringstream ss; + while (!warn_stack.empty()) { + ErrorDiagnostic diag = warn_stack.top(); + + ss << "USDA source near line " << (diag.cursor.row + 1) << ", col " + << (diag.cursor.col + 1) << ": "; + ss << diag.err; // assume message contains newline. + + warn_stack.pop(); + } + + return ss.str(); +} + +// -- end basic + + +// types: Allowd in dict. +// std::string is not included since its represented as StringData or std::string. +// TODO: Include timecode? +#define APPLY_TO_METAVARIABLE_TYPE(__FUNC) \ + __FUNC(value::token) \ + __FUNC(bool) \ + __FUNC(value::half) \ + __FUNC(value::half2) \ + __FUNC(value::half3) \ + __FUNC(value::half4) \ + __FUNC(int32_t) \ + __FUNC(uint32_t) \ + __FUNC(value::int2) \ + __FUNC(value::int3) \ + __FUNC(value::int4) \ + __FUNC(value::uint2) \ + __FUNC(value::uint3) \ + __FUNC(value::uint4) \ + __FUNC(int64_t) \ + __FUNC(uint64_t) \ + __FUNC(float) \ + __FUNC(value::float2) \ + __FUNC(value::float3) \ + __FUNC(value::float4) \ + __FUNC(double) \ + __FUNC(value::double2) \ + __FUNC(value::double3) \ + __FUNC(value::double4) \ + __FUNC(value::matrix2f) \ + __FUNC(value::matrix3f) \ + __FUNC(value::matrix4f) \ + __FUNC(value::matrix2d) \ + __FUNC(value::matrix3d) \ + __FUNC(value::matrix4d) \ + __FUNC(value::quath) \ + __FUNC(value::quatf) \ + __FUNC(value::quatd) \ + __FUNC(value::normal3h) \ + __FUNC(value::normal3f) \ + __FUNC(value::normal3d) \ + __FUNC(value::vector3h) \ + __FUNC(value::vector3f) \ + __FUNC(value::vector3d) \ + __FUNC(value::point3h) \ + __FUNC(value::point3f) \ + __FUNC(value::point3d) \ + __FUNC(value::color3f) \ + __FUNC(value::color3d) \ + __FUNC(value::color4f) \ + __FUNC(value::color4d) \ + __FUNC(value::texcoord2h) \ + __FUNC(value::texcoord2f) \ + __FUNC(value::texcoord2d) \ + __FUNC(value::texcoord3h) \ + __FUNC(value::texcoord3f) \ + __FUNC(value::texcoord3d) + + +bool AsciiParser::ParseDictElement(std::string *out_key, + MetaVariable *out_var) { + (void)out_key; + (void)out_var; + + // dict_element: type (array_qual?) name '=' value + // ; + + std::string type_name; + + if (!ReadIdentifier(&type_name)) { + return false; + } + + if (!SkipWhitespace()) { + return false; + } + + if (!IsSupportedPrimAttrType(type_name)) { + PUSH_ERROR_AND_RETURN("Unknown or unsupported type `" + type_name + "`\n"); + } + + // Has array qualifier? `[]` + bool array_qual = false; + { + char c0, c1; + if (!Char1(&c0)) { + return false; + } + + if (c0 == '[') { + if (!Char1(&c1)) { + return false; + } + + if (c1 == ']') { + array_qual = true; + } else { + // Invalid syntax + PUSH_ERROR_AND_RETURN("Invalid syntax found."); + } + + } else { + if (!Rewind(1)) { + return false; + } + } + } + + if (!SkipWhitespace()) { + return false; + } + + std::string key_name; + if (!ReadIdentifier(&key_name)) { + // string literal is also supported. e.g. "0" + if (ReadStringLiteral(&key_name)) { + // ok + } else { + PUSH_ERROR_AND_RETURN("Failed to parse dictionary key identifier.\n"); + } + } + + if (!SkipWhitespace()) { + return false; + } + + if (!Expect('=')) { + return false; + } + + if (!SkipWhitespace()) { + return false; + } + + uint32_t tyid = value::GetTypeId(type_name); + + primvar::PrimVar var; + + // + // Supports limited types for customData/Dictionary. + // + + // TODO: Unify code with ParseMetaValue() + +#define PARSE_BASE_TYPE(__ty) case value::TypeTraits<__ty>::type_id(): { \ + if (array_qual) { \ + std::vector<__ty> vss; \ + if (!ParseBasicTypeArray(&vss)) { \ + PUSH_ERROR_AND_RETURN(fmt::format("Failed to parse a value of type `{}[]`", value::TypeTraits<__ty>::type_name())); \ + } \ + var.set_value(vss); \ + } else { \ + __ty val; \ + if (!ReadBasicType(&val)) { \ + PUSH_ERROR_AND_RETURN(fmt::format("Failed to parse a value of type `{}`", value::TypeTraits<__ty>::type_name())); \ + } \ + var.set_value(val); \ + } \ + break; \ + } + + switch (tyid) { + APPLY_TO_METAVARIABLE_TYPE(PARSE_BASE_TYPE) + case value::TYPE_ID_STRING: { + if (array_qual) { + std::vector strs; + if (!ParseBasicTypeArray(&strs)) { + PUSH_ERROR_AND_RETURN("Failed to parse `string[]`"); + } + var.set_value(strs); + } else { + value::StringData str; + if (!ReadBasicType(&str)) { + PUSH_ERROR_AND_RETURN("Failed to parse `string`"); + } + var.set_value(str); + } + break; + } + case value::TYPE_ID_ASSET_PATH: { + if (array_qual) { + std::vector arrs; + if (!ParseBasicTypeArray(&arrs)) { + PUSH_ERROR_AND_RETURN("Failed to parse `asset[]`"); + } + var.set_value(arrs); + } else { + value::AssetPath asset; + if (!ReadBasicType(&asset)) { + PUSH_ERROR_AND_RETURN("Failed to parse `asset`"); + } + var.set_value(asset); + } + break; + } + case value::TYPE_ID_DICT: { + Dictionary dict; + + DCOUT("Parse dictionary"); + if (!ParseDict(&dict)) { + PUSH_ERROR_AND_RETURN("Failed to parse `dictionary`"); + } + var.set_value(dict); + break; + } + default: { + PUSH_ERROR_AND_RETURN("Unsupported or invalid type for Metadatum:" + type_name); + } + } + +#undef PARSE_BASE_TYPE + + MetaVariable metavar; + metavar.set_value(key_name, var.value_raw()); + + DCOUT("key: " << key_name << ", type: " << type_name); + + (*out_key) = key_name; + (*out_var) = metavar; + + return true; +} + +bool AsciiParser::MaybeCustom() { + std::string tok; + + auto loc = CurrLoc(); + bool ok = ReadIdentifier(&tok); + + if (!ok) { + // revert + SeekTo(loc); + return false; + } + + if (tok == "custom") { + // cosume `custom` token. + return true; + } + + // revert + SeekTo(loc); + return false; +} + +bool AsciiParser::ParseDict(std::map *out_dict) { + // '{' comment | (type name '=' value)+ '}' + if (!Expect('{')) { + return false; + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + while (!Eof()) { + char c; + if (!Char1(&c)) { + return false; + } + + if (c == '}') { + break; + } else { + if (!Rewind(1)) { + return false; + } + + std::string key; + MetaVariable var; + if (!ParseDictElement(&key, &var)) { + PUSH_ERROR_AND_RETURN("Failed to parse dict element."); + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + if (!var.is_valid()) { + PUSH_ERROR_AND_RETURN("Invalid Dict element(probably internal issue)."); + } + + DCOUT("Add to dict: " << key); + (*out_dict)[key] = var; + } + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + return true; +} + +bool AsciiParser::ParseVariantsElement(std::string *out_key, + std::string *out_var) { + // variants_element: string name '=' value + // ; + + std::string type_name; + + if (!ReadIdentifier(&type_name)) { + return false; + } + + // must be `string` + if (type_name != value::kString) { + PUSH_ERROR_AND_RETURN( + "TinyUSDZ only accepts type `string` for `variants` element."); + } + + if (!SkipWhitespace()) { + return false; + } + + std::string key_name; + if (!ReadIdentifier(&key_name)) { + // string literal is also supported. e.g. "0" + if (ReadStringLiteral(&key_name)) { + // ok + } else { + PUSH_ERROR_AND_RETURN("Failed to parse dictionary key identifier.\n"); + } + } + + if (!SkipWhitespace()) { + return false; + } + + if (!Expect('=')) { + return false; + } + + if (!SkipWhitespace()) { + return false; + } + + std::string var; + if (!ReadBasicType(&var)) { + PUSH_ERROR_AND_RETURN("Failed to parse `string`"); + } + + DCOUT("key: " << key_name << ", value: " << var); + + (*out_key) = key_name; + (*out_var) = var; + + return true; +} + +bool AsciiParser::ParseVariants(VariantSelectionMap *out_map) { + // '{' (string name '=' value)+ '}' + if (!Expect('{')) { + return false; + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + while (!Eof()) { + char c; + if (!Char1(&c)) { + return false; + } + + if (c == '}') { + break; + } else { + if (!Rewind(1)) { + return false; + } + + std::string key; + std::string var; + if (!ParseVariantsElement(&key, &var)) { + PUSH_ERROR_AND_RETURN("Failed to parse an element of `variants`."); + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + DCOUT("Add to variants: " << key); + (*out_map)[key] = var; + } + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + return true; +} + +// 'None' +bool AsciiParser::MaybeNone() { + std::vector buf; + + auto loc = CurrLoc(); + + if (!CharN(4, &buf)) { + SeekTo(loc); + return false; + } + + if ((buf[0] == 'N') && (buf[1] == 'o') && (buf[2] == 'n') && + (buf[3] == 'e')) { + // got it + return true; + } + + SeekTo(loc); + + return false; +} + +bool AsciiParser::MaybeListEditQual(tinyusdz::ListEditQual *qual) { + if (!SkipWhitespace()) { + return false; + } + + std::string tok; + + auto loc = CurrLoc(); + if (!ReadIdentifier(&tok)) { + SeekTo(loc); + return false; + } + + if (tok == "prepend") { + DCOUT("`prepend` list edit qualifier."); + (*qual) = tinyusdz::ListEditQual::Prepend; + } else if (tok == "append") { + DCOUT("`append` list edit qualifier."); + (*qual) = tinyusdz::ListEditQual::Append; + } else if (tok == "add") { + DCOUT("`add` list edit qualifier."); + (*qual) = tinyusdz::ListEditQual::Add; + } else if (tok == "delete") { + DCOUT("`delete` list edit qualifier."); + (*qual) = tinyusdz::ListEditQual::Delete; + } else if (tok == "order") { + DCOUT("`order` list edit qualifier."); + (*qual) = tinyusdz::ListEditQual::Order; + } else { + DCOUT("No ListEdit qualifier."); + // unqualified + // rewind + SeekTo(loc); + (*qual) = tinyusdz::ListEditQual::ResetToExplicit; + } + + if (!SkipWhitespace()) { + return false; + } + + return true; +} + +bool AsciiParser::MaybeVariability(tinyusdz::Variability *variability, bool *varying_authored) { + if (!SkipWhitespace()) { + return false; + } + + std::string tok; + + auto loc = CurrLoc(); + if (!ReadIdentifier(&tok)) { + SeekTo(loc); + return false; + } + + if (tok == "uniform") { + (*variability) = tinyusdz::Variability::Uniform; + (*varying_authored) = false; + } else if (tok == "varying") { + (*variability) = tinyusdz::Variability::Varying; + (*varying_authored) = true; + } else { + (*varying_authored) = false; + // rewind + SeekTo(loc); + } + + if (!SkipWhitespace()) { + return false; + } + + return true; +} + +bool AsciiParser::IsSupportedPrimType(const std::string &ty) { + return _supported_prim_types.count(ty); +} + + +bool AsciiParser::IsSupportedPrimAttrType(const std::string &ty) { + return _supported_prim_attr_types.count(ty); +} + +bool AsciiParser::IsSupportedAPISchema(const std::string &ty) { + return _supported_api_schemas.count(ty); +} + +bool AsciiParser::ReadStringLiteral(std::string *literal) { + std::stringstream ss; + + char c0; + if (!Char1(&c0)) { + return false; + } + + // TODO: Allow triple-quotated string? + + bool single_quote{false}; + + if (c0 == '"') { + // ok + } else if (c0 == '\'') { + // ok + single_quote = true; + } else { + DCOUT("c0 = " << c0); + PUSH_ERROR_AND_RETURN( + "String or Token literal expected but it does not start with \" or '"); + } + + bool end_with_quotation{false}; + + while (!Eof()) { + char c; + if (!Char1(&c)) { + // this should not happen. + return false; + } + + if ((c == '\n') || (c == '\r')) { + PUSH_ERROR_AND_RETURN("New line in string literal."); + } + + if (single_quote) { + if (c == '\'') { + end_with_quotation = true; + break; + } + } else if (c == '"') { + end_with_quotation = true; + break; + } + + ss << c; + } + + if (!end_with_quotation) { + PUSH_ERROR_AND_RETURN( + fmt::format("String literal expected but it does not end with {}.", + single_quote ? "'" : "\"")); + } + + (*literal) = ss.str(); + + _curr_cursor.col += int(literal->size() + 2); // +2 for quotation chars + + return true; +} + +bool AsciiParser::MaybeString(value::StringData *str) { + std::stringstream ss; + + if (!str) { + return false; + } + + auto loc = CurrLoc(); + auto start_cursor = _curr_cursor; + + char c0; + if (!Char1(&c0)) { + SeekTo(loc); + return false; + } + + // ' or " allowed. + if ((c0 != '"') && (c0 != '\'')) { + SeekTo(loc); + return false; + } + + bool single_quote = (c0 == '\''); + + bool end_with_quotation{false}; + + while (!Eof()) { + char c; + if (!Char1(&c)) { + // this should not happen. + SeekTo(loc); + return false; + } + + if ((c == '\n') || (c == '\r')) { + SeekTo(loc); + return false; + } + + if (c == '\\') { + // escaped quote? \" \' + char nc; + if (!LookChar1(&nc)) { + return false; + } + + if (nc == '\'') { + ss << "'"; + _sr->seek_from_current(1); // advance 1 char + continue; + } else if (nc == '"') { + ss << "\""; + _sr->seek_from_current(1); // advance 1 char + continue; + } + } + + if (single_quote) { + if (c == '\'') { + end_with_quotation = true; + break; + } + } else { + if (c == '"') { + end_with_quotation = true; + break; + } + } + + ss << c; + } + + if (!end_with_quotation) { + SeekTo(loc); + return false; + } + + DCOUT("Single quoted string found. col " << start_cursor.col << ", row " + << start_cursor.row); + + size_t displayed_string_len = ss.str().size(); + str->value = unescapeControlSequence(ss.str()); + str->line_col = start_cursor.col; + str->line_row = start_cursor.row; + str->is_triple_quoted = false; + + _curr_cursor.col += int(displayed_string_len + 2); // +2 for quotation chars + + return true; +} + +bool AsciiParser::MaybeTripleQuotedString(value::StringData *str) { + std::stringstream ss; + + auto loc = CurrLoc(); + auto start_cursor = _curr_cursor; + + std::vector triple_quote; + if (!CharN(3, &triple_quote)) { + SeekTo(loc); + return false; + } + + if (triple_quote.size() != 3) { + SeekTo(loc); + return false; + } + + bool single_quote = false; + + if (triple_quote[0] == '"' && triple_quote[1] == '"' && + triple_quote[2] == '"') { + // ok + } else if (triple_quote[0] == '\'' && triple_quote[1] == '\'' && + triple_quote[2] == '\'') { + // ok + single_quote = true; + } else { + SeekTo(loc); + return false; + } + + // Read until next triple-quote `"""` or "'''" + std::stringstream str_buf; + + auto locinfo = _curr_cursor; + + int single_quote_count = 0; // ' + int double_quote_count = 0; // " + + bool got_closing_triple_quote{false}; + + while (!Eof()) { + char c; + + if (!Char1(&c)) { + SeekTo(loc); + return false; + } + + // Seek \""" or \''' + // Unescape '\' + if (c == '\\') { + std::vector buf(3, '\0'); + if (!LookCharN(3, &buf)) { + // at least 3 chars should be read + return false; + } + + if (buf[0] == '\'' && + buf[1] == '\'' && + buf[2] == '\'') { + str_buf << "'''"; + // advance + _sr->seek_from_current(3); + locinfo.col += 3; + continue; + } else if (buf[0] == '"' && + buf[1] == '"' && + buf[2] == '"') { + str_buf << "\"\"\""; + // advance + _sr->seek_from_current(3); + locinfo.col += 3; + continue; + } + } + + str_buf << c; + + if (c == '"') { + double_quote_count++; + single_quote_count = 0; + } else if (c == '\'') { + double_quote_count = 0; + single_quote_count++; + } else { + double_quote_count = 0; + single_quote_count = 0; + } + + + // Update loc info + locinfo.col++; + if (c == '\n') { + locinfo.col = 0; + locinfo.row++; + } else if (c == '\r') { + // CRLF? + if (_sr->tell() < (_sr->size() - 1)) { + char d; + if (!Char1(&d)) { + // this should not happen. + SeekTo(loc); + return false; + } + + if (d == '\n') { + // CRLF + str_buf << d; + } else { + // unwind 1 char + if (!_sr->seek_from_current(-1)) { + // this should not happen. + SeekTo(loc); + return false; + } + } + } + locinfo.col = 0; + locinfo.row++; + } + + if (double_quote_count == 3) { + // got '"""' + if (single_quote) { + // continue + } else { + got_closing_triple_quote = true; + break; + } + } + if (single_quote_count == 3) { + // got ''' + if (double_quote_count) { + // continue + } else { + got_closing_triple_quote = true; + break; + } + } + } + + if (!got_closing_triple_quote) { + SeekTo(loc); + return false; + } + + DCOUT("single_quote = " << single_quote); + DCOUT("Triple quoted string found. col " << start_cursor.col << ", row " + << start_cursor.row); + + // remove last '"""' or ''' + str->single_quote = single_quote; + std::string s = str_buf.str(); + if (s.size() > 3) { // just in case + s.erase(s.size() - 3); + } + + DCOUT("str = " << s); + + str->value = unescapeControlSequence(s); + + DCOUT("unescape str = " << str->value); + + str->line_col = start_cursor.col; + str->line_row = start_cursor.row; + str->is_triple_quoted = true; + + _curr_cursor = locinfo; + + return true; +} + +bool AsciiParser::ReadPrimAttrIdentifier(std::string *token) { + // Example: + // - xformOp:transform + // - primvars:uvmap1 + + std::stringstream ss; + + while (!Eof()) { + char c; + if (!Char1(&c)) { + // this should not happen. + return false; + } + + if (c == '_') { + // ok + } else if (c == ':') { // namespace + // ':' must lie in the middle of string literal + if (ss.str().size() == 0) { + PUSH_ERROR_AND_RETURN("PrimAttr name must not starts with `:`"); + } + } else if (c == '.') { // delimiter for `connect` + // '.' must lie in the middle of string literal + if (ss.str().size() == 0) { + PUSH_ERROR_AND_RETURN("PrimAttr name must not starts with `.`"); + } + } else if (std::isalnum(int(c))) { + // number must not be allowed for the first char. + if (ss.str().size() == 0) { + if (!std::isalpha(int(c))) { + PUSH_ERROR_AND_RETURN("PrimAttr name must not starts with number."); + } + } + } else { + _sr->seek_from_current(-1); + break; + } + + _curr_cursor.col++; + + ss << c; + } + + { + std::string name_err; + if (!pathutil::ValidatePropPath(Path("", ss.str()), &name_err)) { + PUSH_ERROR_AND_RETURN_TAG(kAscii, + fmt::format("Invalid Property name `{}`: {}", ss.str(), name_err)); + } + } + + // '.' must lie in the middle of string literal + if (ss.str().back() == '.') { + PUSH_ERROR_AND_RETURN("PrimAttr name must not ends with `.`\n"); + return false; + } + + + std::string tok = ss.str(); + + if (contains(tok, '.')) { + if (endsWith(tok, ".connect") || endsWith(tok, ".timeSamples")) { + // OK + } else { + PUSH_ERROR_AND_RETURN_TAG( + kAscii, fmt::format("Must ends with `.connect` or `.timeSamples` for " + "attrbute name: `{}`", + tok)); + } + + // Multiple `.` is not allowed(e.g. attr.connect.timeSamples) + if (counts(tok, '.') > 1) { + PUSH_ERROR_AND_RETURN_TAG( + kAscii, fmt::format("Attribute identifier `{}` containing multiple " + "`.` is not allowed.", + tok)); + } + } + + (*token) = ss.str(); + DCOUT("primAttr identifier = " << (*token)); + return true; +} + +bool AsciiParser::ReadIdentifier(std::string *token) { + // identifier = (`_` | [a-zA-Z]) (`_` | [a-zA-Z0-9]+) + std::stringstream ss; + + // The first character. + { + char c; + if (!Char1(&c)) { + // this should not happen. + DCOUT("read1 failed."); + return false; + } + + if (c == '_') { + // ok + } else if (!std::isalpha(int(c))) { + DCOUT(fmt::format("Invalid identiefier: '{}'", c)); + _sr->seek_from_current(-1); + return false; + } + _curr_cursor.col++; + + ss << c; + } + + while (!Eof()) { + char c; + if (!Char1(&c)) { + // this should not happen. + return false; + } + + if (c == '_') { + // ok + } else if (!std::isalnum(int(c))) { + _sr->seek_from_current(-1); + break; // end of identifier(e.g. ' ') + } + + _curr_cursor.col++; + + ss << c; + } + + (*token) = ss.str(); + return true; +} + +bool AsciiParser::ReadPathIdentifier(std::string *path_identifier) { + // path_identifier = `<` string `>` + std::stringstream ss; + + if (!Expect('<')) { + return false; + } + + if (!SkipWhitespace()) { + return false; + } + + // read until '>' + bool ok = false; + while (!Eof()) { + char c; + if (!Char1(&c)) { + // this should not happen. + return false; + } + + if (c == '>') { + // end + ok = true; + _curr_cursor.col++; + break; + } + + // TODO: Check if character is valid for path identifier + ss << c; + } + + if (!ok) { + return false; + } + + (*path_identifier) = TrimString(ss.str()); + // std::cout << "PathIdentifier: " << (*path_identifier) << "\n"; + + return true; +} + +bool AsciiParser::ReadUntilNewline(std::string *str) { + + std::stringstream ss; + + while (!Eof()) { + char c; + if (!Char1(&c)) { + // this should not happen. + return false; + } + + if (c == '\n') { + break; + } else if (c == '\r') { + // CRLF? + if (_sr->tell() < (_sr->size() - 1)) { + char d; + if (!Char1(&d)) { + // this should not happen. + return false; + } + + if (d == '\n') { + break; + } + + // unwind 1 char + if (!_sr->seek_from_current(-1)) { + // this should not happen. + return false; + } + + break; + } + + } + + ss << c; + } + + _curr_cursor.row++; + _curr_cursor.col = 0; + + (*str) = ss.str(); + + return true; +} + +bool AsciiParser::SkipUntilNewline() { + while (!Eof()) { + char c; + if (!Char1(&c)) { + // this should not happen. + return false; + } + + if (c == '\n') { + break; + } else if (c == '\r') { + // CRLF? + if (_sr->tell() < (_sr->size() - 1)) { + char d; + if (!Char1(&d)) { + // this should not happen. + return false; + } + + if (d == '\n') { + break; + } + + // unwind 1 char + if (!_sr->seek_from_current(-1)) { + // this should not happen. + return false; + } + + break; + } + + } else { + // continue + } + } + + _curr_cursor.row++; + _curr_cursor.col = 0; + return true; +} + +// metadata_opt := string_literal '\n' +// | var '=' value '\n' +// +bool AsciiParser::ParseStageMetaOpt() { + // Maybe string-only comment. + // Comment cannot have multiple lines. The last one wins + { + value::StringData str; + if (MaybeTripleQuotedString(&str)) { + _stage_metas.comment = str; + return true; + } else if (MaybeString(&str)) { + _stage_metas.comment = str; + return true; + } + } + + std::string varname; + if (!ReadIdentifier(&varname)) { + return false; + } + + DCOUT("varname = " << varname); + + if (!IsStageMeta(varname)) { + std::string msg = "'" + varname + "' is not a Stage Metadata variable.\n"; + PUSH_ERROR_AND_RETURN(msg); + return false; + } + + if (!Expect('=')) { + PUSH_ERROR_AND_RETURN("'=' expected in Stage Metadata opt."); + return false; + } + + if (!SkipWhitespace()) { + return false; + } + + const VariableDef &vardef = _supported_stage_metas.at(varname); + MetaVariable var; + if (!ParseMetaValue(vardef, &var)) { + PUSH_ERROR_AND_RETURN("Failed to parse meta value.\n"); + return false; + } + var.set_name(varname); + + if (varname == "defaultPrim") { + value::token tok; + if (var.get_value(&tok)) { + DCOUT("defaultPrim = " << tok); + _stage_metas.defaultPrim = tok; + } else { + PUSH_ERROR_AND_RETURN("`defaultPrim` isn't a token value."); + } + } else if (varname == "subLayers") { + std::vector paths; + if (var.get_value(&paths)) { + DCOUT("subLayers = " << paths); + for (const auto &item : paths) { + _stage_metas.subLayers.push_back(item); + } + } else { + PUSH_ERROR_AND_RETURN("`subLayers` isn't an array of asset path"); + } + } else if (varname == "upAxis") { + if (auto pv = var.get_value()) { + DCOUT("upAxis = " << pv.value()); + const std::string s = pv.value().str(); + if (s == "X") { + _stage_metas.upAxis = Axis::X; + } else if (s == "Y") { + _stage_metas.upAxis = Axis::Y; + } else if (s == "Z") { + _stage_metas.upAxis = Axis::Z; + } else { + if (_option.strict_allowedToken_check) { + PUSH_ERROR_AND_RETURN( + "Invalid `upAxis` value. Must be \"X\", \"Y\" or \"Z\", but got " + "\"" + + s + "\"(Note: Case sensitive)"); + } else { + PUSH_WARN("Ignore unknown `upAxis` value. Must be \"X\", \"Y\" or \"Z\", but got " + "\"" + + s + "\"(Note: Case sensitive). Use default upAxis `Y`."); + _stage_metas.upAxis = Axis::Y; + } + } + } else { + PUSH_ERROR_AND_RETURN("`upAxis` isn't a token value."); + } + } else if ((varname == "doc") || (varname == "documentation")) { + // `documentation` will be shorten to `doc` + if (auto pv = var.get_value()) { + DCOUT("doc = " << to_string(pv.value())); + _stage_metas.doc = pv.value(); + } else if (auto pvs = var.get_value()) { + value::StringData sdata; + sdata.value = pvs.value(); + sdata.is_triple_quoted = false; + _stage_metas.doc = sdata; + } else { + PUSH_ERROR_AND_RETURN(fmt::format("`{}` isn't a string value.", varname)); + } + } else if (varname == "metersPerUnit") { + DCOUT("ty = " << var.type_name()); + if (auto pv = var.get_value()) { + DCOUT("metersPerUnit = " << pv.value()); + _stage_metas.metersPerUnit = double(pv.value()); + } else if (auto pvd = var.get_value()) { + DCOUT("metersPerUnit = " << pvd.value()); + _stage_metas.metersPerUnit = pvd.value(); + } else { + PUSH_ERROR_AND_RETURN("`metersPerUnit` isn't a floating-point value."); + } + } else if (varname == "timeCodesPerSecond") { + DCOUT("ty = " << var.type_name()); + if (auto pv = var.get_value()) { + DCOUT("metersPerUnit = " << pv.value()); + _stage_metas.timeCodesPerSecond = double(pv.value()); + } else if (auto pvd = var.get_value()) { + DCOUT("metersPerUnit = " << pvd.value()); + _stage_metas.timeCodesPerSecond = pvd.value(); + } else { + PUSH_ERROR_AND_RETURN( + "`timeCodesPerSecond` isn't a floating-point value."); + } + } else if (varname == "startTimeCode") { + if (auto pv = var.get_value()) { + DCOUT("startTimeCode = " << pv.value()); + _stage_metas.startTimeCode = double(pv.value()); + } else if (auto pvd = var.get_value()) { + DCOUT("startTimeCode = " << pvd.value()); + _stage_metas.startTimeCode = pvd.value(); + } + } else if (varname == "endTimeCode") { + if (auto pv = var.get_value()) { + DCOUT("endTimeCode = " << pv.value()); + _stage_metas.endTimeCode = double(pv.value()); + } else if (auto pvd = var.get_value()) { + DCOUT("endTimeCode = " << pvd.value()); + _stage_metas.endTimeCode = pvd.value(); + } + } else if (varname == "framesPerSecond") { + if (auto pv = var.get_value()) { + DCOUT("framesPerSecond = " << pv.value()); + _stage_metas.framesPerSecond = double(pv.value()); + } else if (auto pvd = var.get_value()) { + DCOUT("framesPerSecond = " << pvd.value()); + _stage_metas.framesPerSecond = pvd.value(); + } + } else if (varname == "apiSchemas") { + // TODO: ListEdit qualifer check + if (auto pv = var.get_value>()) { + for (auto &item : pv.value()) { + if (IsSupportedAPISchema(item.str())) { + // OK + } else { + PUSH_ERROR_AND_RETURN("\"" << item.str() + << "\" is not supported(at the moment) " + "for `apiSchemas` in TinyUSDZ."); + } + } + } else { + PUSH_ERROR_AND_RETURN("`apiSchemas` isn't an `token[]` type."); + } + } else if (varname == "customLayerData") { + if (auto pv = var.get_value()) { + _stage_metas.customLayerData = pv.value(); + } else { + PUSH_ERROR_AND_RETURN("`customLayerData` isn't a dictionary value."); + } + } else if (varname == "comment") { + if (auto pv = var.get_value()) { + DCOUT("comment = " << to_string(pv.value())); + _stage_metas.comment = pv.value(); + } else if (auto pvs = var.get_value()) { + value::StringData sdata; + sdata.value = pvs.value(); + sdata.is_triple_quoted = false; + _stage_metas.comment = sdata; + } else { + PUSH_ERROR_AND_RETURN(fmt::format("`{}` isn't a string value.", varname)); + } + } else { + DCOUT("TODO: Stage meta: " << varname); + PUSH_WARN("TODO: Stage meta: " << varname); + } + + return true; +} + +// Parse Stage meta +// meta = '(' (comment | metadata_opt)+ ')' +// ; +bool AsciiParser::ParseStageMetas() { + if (!Expect('(')) { + return false; + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + while (!Eof()) { + char c; + if (!LookChar1(&c)) { + return false; + } + + if (c == ')') { + if (!SeekTo(CurrLoc() + 1)) { + return false; + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + DCOUT("Stage metas end"); + + // end + return true; + + } else { + if (!SkipCommentAndWhitespaceAndNewline()) { + // eof + return false; + } + + if (!ParseStageMetaOpt()) { + // parse error + return false; + } + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + } + + DCOUT("ParseStageMetas end"); + return true; +} + +// `#` style comment +bool AsciiParser::ParseSharpComment() { + char c; + if (!Char1(&c)) { + // eol + return false; + } + + if (c != '#') { + return false; + } + + return true; +} + +// Fetch 1 char. Do not change input stream position. +bool AsciiParser::LookChar1(char *c) { + if (!Char1(c)) { + return false; + } + + Rewind(1); + + return true; +} + +// Fetch N chars. Do not change input stream position. +bool AsciiParser::LookCharN(size_t n, std::vector *nc) { + std::vector buf(n); + + auto loc = CurrLoc(); + + bool ok = _sr->read(n, n, reinterpret_cast(buf.data())); + if (ok) { + (*nc) = buf; + } + + SeekTo(loc); + + return ok; +} + +bool AsciiParser::Char1(char *c) { return _sr->read1(c); } + +bool AsciiParser::CharN(size_t n, std::vector *nc) { + std::vector buf(n); + + bool ok = _sr->read(n, n, reinterpret_cast(buf.data())); + if (ok) { + (*nc) = buf; + } + + return ok; +} + +bool AsciiParser::Rewind(size_t offset) { + if (!_sr->seek_from_current(-int64_t(offset))) { + return false; + } + + return true; +} + +uint64_t AsciiParser::CurrLoc() { return _sr->tell(); } + +bool AsciiParser::SeekTo(uint64_t pos) { + if (!_sr->seek_set(pos)) { + return false; + } + + return true; +} + +bool AsciiParser::PushParserState() { + // Stack size must be less than the number of input bytes. + if (parse_stack.size() >= _sr->size()) { + PUSH_ERROR_AND_RETURN_TAG(kAscii, "Parser state stack become too deep."); + } + + uint64_t loc = _sr->tell(); + + ParseState state; + state.loc = int64_t(loc); + parse_stack.push(state); + + return true; +} + +bool AsciiParser::PopParserState(ParseState *state) { + if (parse_stack.empty()) { + return false; + } + + (*state) = parse_stack.top(); + + parse_stack.pop(); + + return true; +} + +bool AsciiParser::SkipWhitespace() { + while (!Eof()) { + char c; + if (!Char1(&c)) { + // this should not happen. + return false; + } + _curr_cursor.col++; + + if ((c == ' ') || (c == '\t') || (c == '\f')) { + // continue + } else { + break; + } + } + + // unwind 1 char + if (!_sr->seek_from_current(-1)) { + return false; + } + _curr_cursor.col--; + + return true; +} + +bool AsciiParser::SkipWhitespaceAndNewline(const bool allow_semicolon) { + // USDA also allow C-style ';' as a newline separator. + while (!Eof()) { + char c; + if (!Char1(&c)) { + // this should not happen. + return false; + } + + // printf("sws c = %c\n", c); + + if ((c == ' ') || (c == '\t') || (c == '\f')) { + _curr_cursor.col++; + // continue + } else if (allow_semicolon && (c == ';')) { + _curr_cursor.col++; + // continue + } else if (c == '\n') { + _curr_cursor.col = 0; + _curr_cursor.row++; + // continue + } else if (c == '\r') { + // CRLF? + if (_sr->tell() < (_sr->size() - 1)) { + char d; + if (!Char1(&d)) { + // this should not happen. + return false; + } + + if (d == '\n') { + // CRLF + } else { + // unwind 1 char + if (!_sr->seek_from_current(-1)) { + // this should not happen. + return false; + } + } + } + _curr_cursor.col = 0; + _curr_cursor.row++; + // continue + } else { + // end loop + if (!_sr->seek_from_current(-1)) { + return false; + } + break; + } + } + + return true; +} + +bool AsciiParser::SkipCommentAndWhitespaceAndNewline(const bool allow_semicolon) { + // Skip multiple line of comments. + while (!Eof()) { + char c; + if (!Char1(&c)) { + // this should not happen. + return false; + } + + // printf("sws c = %c\n", c); + + if (c == '#') { + if (!SkipUntilNewline()) { + return false; + } + } else if ((c == ' ') || (c == '\t') || (c == '\f')) { + _curr_cursor.col++; + // continue + } else if (allow_semicolon && (c == ';')) { + _curr_cursor.col++; + // continue + } else if (c == '\n') { + _curr_cursor.col = 0; + _curr_cursor.row++; + // continue + } else if (c == '\r') { + // CRLF? + if (_sr->tell() < (_sr->size() - 1)) { + char d; + if (!Char1(&d)) { + // this should not happen. + return false; + } + + if (d == '\n') { + // CRLF + } else { + // unwind 1 char + if (!_sr->seek_from_current(-1)) { + // this should not happen. + return false; + } + } + } + _curr_cursor.col = 0; + _curr_cursor.row++; + // continue + } else { + // std::cout << "unwind\n"; + // end loop + if (!_sr->seek_from_current(-1)) { + return false; + } + break; + } + } + + return true; +} + +bool AsciiParser::Expect(char expect_c) { + if (!SkipWhitespace()) { + return false; + } + + char c; + if (!Char1(&c)) { + // this should not happen. + return false; + } + + bool ret = (c == expect_c); + + if (!ret) { + std::string msg = "Expected `" + std::string(&expect_c, 1) + "` but got `" + + std::string(&c, 1) + "`\n"; + PUSH_ERROR_AND_RETURN(msg); + + // unwind + _sr->seek_from_current(-1); + } else { + _curr_cursor.col++; + } + + return ret; +} + +// Parse magic +// #usda FLOAT +bool AsciiParser::ParseMagicHeader() { + if (!SkipWhitespace()) { + return false; + } + + if (Eof()) { + return false; + } + + { + char magic[6]; + if (!_sr->read(6, 6, reinterpret_cast(magic))) { + // eol + return false; + } + + if ((magic[0] == '#') && (magic[1] == 'u') && (magic[2] == 's') && + (magic[3] == 'd') && (magic[4] == 'a') && (magic[5] == ' ')) { + // ok + } else { + PUSH_ERROR_AND_RETURN( + "Magic header must start with `#usda `(at least single whitespace " + "after 'a') but got `" + + std::string(magic, 6)); + } + } + + if (!SkipWhitespace()) { + // eof + return false; + } + + // current we only accept "1.0" + { + char ver[3]; + if (!_sr->read(3, 3, reinterpret_cast(ver))) { + return false; + } + + if ((ver[0] == '1') && (ver[1] == '.') && (ver[2] == '0')) { + // ok + _version = 1.0f; + } else { + PUSH_ERROR_AND_RETURN("Version must be `1.0` but got `" + + std::string(ver, 3) + "`"); + } + } + + SkipUntilNewline(); + + return true; +} + +bool AsciiParser::ParseCustomMetaValue() { + // type identifier '=' value + + // return ParseAttributeMeta(); + PUSH_ERROR_AND_RETURN("TODO"); +} + +bool AsciiParser::ParseAssetIdentifier(value::AssetPath *out, + bool *triple_deliminated) { + // '..' or "..." are also allowed. + // @...@ + // or @@@...@@@ (Triple '@'-deliminated asset identifier.) + // @@@ = Path containing '@'. '@@@' in Path is encoded as '\@@@' + // + // Example: + // @bora@ + // @@@bora@@@ + // @@@bora\@@@dora@@@ + + // TODO: Correctly support escape characters + + // look ahead. + std::vector buf; + uint64_t curr = _sr->tell(); + bool maybe_triple{false}; + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + if (CharN(3, &buf)) { + if (buf[0] == '@' && buf[1] == '@' && buf[2] == '@') { + maybe_triple = true; + } + } + + bool valid{false}; + + if (!maybe_triple) { + // delimiter = " ' @ + + SeekTo(curr); + char s; + if (!Char1(&s)) { + return false; + } + + char delim = s; + + if ((s == '@') || (s == '\'') || (s == '"')) { + // ok + } else { + std::string sstr{s}; + PUSH_ERROR_AND_RETURN("Asset must start with '@', '\'' or '\"', but got '" + sstr + + "'"); + } + + std::string tok; + + // Read until next delimiter + bool found_delimiter = false; + while (!Eof()) { + char c; + + if (!Char1(&c)) { + return false; + } + + if (c == delim) { + found_delimiter = true; + break; + } + + tok += c; + } + + if (found_delimiter) { + (*out) = tok; + (*triple_deliminated) = false; + + valid = true; + } + + } else { + bool found_delimiter{false}; + bool escape_sequence{false}; + int at_cnt{0}; + std::string tok; + + // Read until '@@@' appears + // Need to escaped '@@@'("\\@@@") + while (!Eof()) { + char c; + + if (!Char1(&c)) { + return false; + } + + if (c == '\\') { + escape_sequence = true; + } + + if (c == '@') { + at_cnt++; + } else { + at_cnt--; + if (at_cnt < 0) { + at_cnt = 0; + } + } + + tok += c; + + if (at_cnt == 3) { + if (escape_sequence) { + // Still in path identifier... + // Unescape "\\@@@" + + if (tok.size() > 3) { // this should be true. + if (endsWith(tok, "\\@@@")) { // this also should be true. + tok.erase(tok.size() - 4); + tok.append("@@@"); + } + } + at_cnt = 0; + escape_sequence = false; + } else { + // Got it. '@@@' + found_delimiter = true; + break; + } + } + } + + if (found_delimiter) { + // remote last '@@@' + (*out) = removeSuffix(tok, "@@@"); + (*triple_deliminated) = true; + + valid = true; + } + } + + return valid; +} + +bool AsciiParser::ParseReference(Reference *out, bool *triple_deliminated) { + /* + Asset reference = AsssetIdentifier + optially followd by prim path + + AssetIdentifier could be empty(self-reference?) + + Example: + "bora" + @bora@ + @bora@ + + */ + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + // Parse AssetIdentifier + { + char nc; + if (!LookChar1(&nc)) { + return false; + } + + if (nc == '<') { + // No Asset Identifier. + out->asset_path = value::AssetPath(""); + } else { + + value::AssetPath ap; + if (!ParseAssetIdentifier(&ap, triple_deliminated)) { + PUSH_ERROR_AND_RETURN_TAG(kAscii, "Failed to parse asset path identifier."); + } + out->asset_path = ap; + } + } + + // Parse optional prim_path + if (!SkipWhitespace()) { + return false; + } + + { + char c; + if (!Char1(&c)) { + return false; + } + + if (c == '<') { + if (!Rewind(1)) { + return false; + } + + std::string path; + if (!ReadPathIdentifier(&path)) { + return false; + } + + out->prim_path = Path(path, ""); + } else { + if (!Rewind(1)) { + return false; + } + } + } + + // TODO: LayerOffset and CustomData + + return true; +} + +bool AsciiParser::ParsePayload(Payload *out, bool *triple_deliminated) { + // Reference, but no customData. + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + // Parse AssetIdentifier + { + char nc; + if (!LookChar1(&nc)) { + return false; + } + + if (nc == '<') { + // No Asset Identifier. + out->asset_path = value::AssetPath(""); + } else { + + value::AssetPath ap; + if (!ParseAssetIdentifier(&ap, triple_deliminated)) { + PUSH_ERROR_AND_RETURN_TAG(kAscii, "Failed to parse asset path identifier."); + } + out->asset_path = ap; + } + } + + // Parse optional prim_path + if (!SkipWhitespace()) { + return false; + } + + { + char c; + if (!Char1(&c)) { + return false; + } + + if (c == '<') { + if (!Rewind(1)) { + return false; + } + + std::string path; + if (!ReadPathIdentifier(&path)) { + return false; + } + + out->prim_path = Path(path, ""); + } else { + if (!Rewind(1)) { + return false; + } + } + } + + // TODO: LayerOffset + + return true; +} + +bool AsciiParser::ParseMetaValue(const VariableDef &def, MetaVariable *outvar) { + std::string vartype = def.type; + const std::string varname = def.name; + + MetaVariable var; + + bool array_qual{false}; + + DCOUT("parseMeta: vartype " << vartype); + + if (endsWith(vartype, "[]")) { + vartype = removeSuffix(vartype, "[]"); + array_qual = true; + } else if (def.allow_array_type) { // variable can be array + // Seek '[' + char c; + if (LookChar1(&c)) { + if (c == '[') { + array_qual = true; + } + } + } + + uint32_t tyid = value::GetTypeId(vartype); + +#define PARSE_BASE_TYPE(__ty) case value::TypeTraits<__ty>::type_id(): { \ + if (array_qual) { \ + std::vector<__ty> vss; \ + if (!ParseBasicTypeArray(&vss)) { \ + PUSH_ERROR_AND_RETURN(fmt::format("Failed to parse a value of type `{}[]`", value::TypeTraits<__ty>::type_name())); \ + } \ + var.set_value(vss); \ + } else { \ + __ty val; \ + if (!ReadBasicType(&val)) { \ + PUSH_ERROR_AND_RETURN(fmt::format("Failed to parse a value of type `{}`", value::TypeTraits<__ty>::type_name())); \ + } \ + var.set_value(val); \ + } \ + break; \ + } + + // Special treatment for "Reference" and "Payload" + if (vartype == "Reference") { + if (array_qual) { + std::vector refs; + if (!ParseBasicTypeArray(&refs)) { + PUSH_ERROR_AND_RETURN_TAG( + kAscii, + fmt::format("Failed to parse `{}` in Prim metadataum.", def.name)); + } + var.set_value(refs); + } else { + nonstd::optional ref; + if (!ReadBasicType(&ref)) { + PUSH_ERROR_AND_RETURN_TAG( + kAscii, + fmt::format("Failed to parse `{}` in Prim metadataum.", def.name)); + } + if (ref) { + var.set_value(ref.value()); + } else { + // None + var.set_value(value::ValueBlock()); + } + } + } else if (vartype == "Payload") { + if (array_qual) { + std::vector refs; + if (!ParseBasicTypeArray(&refs)) { + PUSH_ERROR_AND_RETURN_TAG( + kAscii, + fmt::format("Failed to parse `{}` in Prim metadataum.", def.name)); + } + var.set_value(refs); + } else { + nonstd::optional ref; + if (!ReadBasicType(&ref)) { + PUSH_ERROR_AND_RETURN_TAG( + kAscii, + fmt::format("Failed to parse `{}` in Prim metadataum.", def.name)); + } + if (ref) { + var.set_value(ref.value()); + } else { + // None + var.set_value(value::ValueBlock()); + } + } + } else if (vartype == value::kPath) { + if (array_qual) { + std::vector paths; + if (!ParseBasicTypeArray(&paths)) { + PUSH_ERROR_AND_RETURN_TAG( + kAscii, + fmt::format("Failed to parse `{}` in Prim metadatum.", def.name)); + } + var.set_value(paths); + + } else { + Path path; + if (!ReadBasicType(&path)) { + PUSH_ERROR_AND_RETURN_TAG( + kAscii, + fmt::format("Failed to parse `{}` in Prim metadatum.", def.name)); + } + var.set_value(path); + } + } else { + switch (tyid) { + APPLY_TO_METAVARIABLE_TYPE(PARSE_BASE_TYPE) + case value::TYPE_ID_STRING: { + if (array_qual) { + std::vector strs; + if (!ParseBasicTypeArray(&strs)) { + PUSH_ERROR_AND_RETURN("Failed to parse `string[]`"); + } + var.set_value(strs); + } else { + std::string str; + if (!ReadBasicType(&str)) { + PUSH_ERROR_AND_RETURN("Failed to parse `string`"); + } + var.set_value(str); + } + break; + } + case value::TYPE_ID_ASSET_PATH: { + if (array_qual) { + std::vector arrs; + if (!ParseBasicTypeArray(&arrs)) { + PUSH_ERROR_AND_RETURN("Failed to parse `asset[]`"); + } + var.set_value(arrs); + } else { + value::AssetPath asset; + if (!ReadBasicType(&asset)) { + PUSH_ERROR_AND_RETURN("Failed to parse `asset`"); + } + var.set_value(asset); + } + break; + } + case value::TYPE_ID_DICT: { + Dictionary dict; + + DCOUT("Parse dictionary"); + if (!ParseDict(&dict)) { + PUSH_ERROR_AND_RETURN("Failed to parse `dictionary`"); + } + var.set_value(dict); + break; + } + default: { + std::string tyname = vartype; + if (array_qual) { + tyname += "[]"; + } + PUSH_ERROR_AND_RETURN("Unsupported or invalid type for Metadatum:" + tyname); + } + } + } + +#undef PARSE_BASE_TYPE + + (*outvar) = var; + + return true; +} + +bool AsciiParser::LexFloat(std::string *result) { + // FLOATVAL : ('+' or '-')? FLOAT + // FLOAT + // : ('0'..'9')+ '.' ('0'..'9')* EXPONENT? + // | '.' ('0'..'9')+ EXPONENT? + // | ('0'..'9')+ EXPONENT + // ; + // EXPONENT : ('e'|'E') ('+'|'-')? ('0'..'9')+ ; + + std::stringstream ss; + + bool has_sign{false}; + bool leading_decimal_dots{false}; + { + char sc; + if (!Char1(&sc)) { + return false; + } + _curr_cursor.col++; + + // sign, '.' or [0-9] + if ((sc == '+') || (sc == '-')) { + ss << sc; + has_sign = true; + + char c; + if (!Char1(&c)) { + return false; + } + + if (c == '.') { + // ok. something like `+.7`, `-.53` + leading_decimal_dots = true; + _curr_cursor.col++; + ss << c; + + } else { + // unwind and continue + _sr->seek_from_current(-1); + } + + } else if ((sc >= '0') && (sc <= '9')) { + // ok + ss << sc; + } else if (sc == '.') { + // ok but rescan again in 2. + leading_decimal_dots = true; + if (!Rewind(1)) { + return false; + } + _curr_cursor.col--; + } else { + PUSH_ERROR_AND_RETURN("Sign or `.` or 0-9 expected."); + } + } + + (void)has_sign; + + // 1. Read the integer part + char curr; + if (!leading_decimal_dots) { + // std::cout << "1 read int part: ss = " << ss.str() << "\n"; + + while (!Eof()) { + if (!Char1(&curr)) { + return false; + } + + // std::cout << "1 curr = " << curr << "\n"; + if ((curr >= '0') && (curr <= '9')) { + // continue + ss << curr; + } else { + _sr->seek_from_current(-1); + break; + } + } + } + + if (Eof()) { + (*result) = ss.str(); + return true; + } + + if (!Char1(&curr)) { + return false; + } + + // std::cout << "before 2: ss = " << ss.str() << ", curr = " << curr << + // "\n"; + + // 2. Read the decimal part + if (curr == '.') { + ss << curr; + + while (!Eof()) { + if (!Char1(&curr)) { + return false; + } + + if ((curr >= '0') && (curr <= '9')) { + ss << curr; + } else { + break; + } + } + + } else if ((curr == 'e') || (curr == 'E')) { + // go to 3. + } else { + // end + (*result) = ss.str(); + _sr->seek_from_current(-1); + return true; + } + + + if (Eof()) { + (*result) = ss.str(); + return true; + } + + // 3. Read the exponent part + bool has_exp_sign{false}; + if ((curr == 'e') || (curr == 'E')) { + ss << curr; + + if (!Char1(&curr)) { + return false; + } + + if ((curr == '+') || (curr == '-')) { + // exp sign + ss << curr; + has_exp_sign = true; + + } else if ((curr >= '0') && (curr <= '9')) { + // ok + ss << curr; + } else { + // Empty E is not allowed. + PUSH_ERROR_AND_RETURN("Empty `E' is not allowed."); + } + + while (!Eof()) { + if (!Char1(&curr)) { + return false; + } + + if ((curr >= '0') && (curr <= '9')) { + // ok + ss << curr; + + } else if ((curr == '+') || (curr == '-')) { + if (has_exp_sign) { + // No multiple sign characters + PUSH_ERROR_AND_RETURN("No multiple exponential sign characters."); + } + + ss << curr; + has_exp_sign = true; + } else { + // end + _sr->seek_from_current(-1); + break; + } + } + } else { + _sr->seek_from_current(-1); + } + + (*result) = ss.str(); + return true; +} + +nonstd::optional AsciiParser::GetStageMetaDefinition( + const std::string &name) { + if (_supported_stage_metas.count(name)) { + return _supported_stage_metas.at(name); + } + + return nonstd::nullopt; +} + +nonstd::optional AsciiParser::GetPrimMetaDefinition( + const std::string &name) { + if (_supported_prim_metas.count(name)) { + return _supported_prim_metas.at(name); + } + + return nonstd::nullopt; +} + +nonstd::optional AsciiParser::GetPropMetaDefinition( + const std::string &name) { + if (_supported_prop_metas.count(name)) { + return _supported_prop_metas.at(name); + } + + return nonstd::nullopt; +} + +bool AsciiParser::ParseStageMeta(std::pair *out) { + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + tinyusdz::ListEditQual qual{ListEditQual::ResetToExplicit}; + if (!MaybeListEditQual(&qual)) { + return false; + } + + DCOUT("list-edit qual: " << tinyusdz::to_string(qual)); + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + std::string varname; + if (!ReadIdentifier(&varname)) { + return false; + } + + // std::cout << "varname = `" << varname << "`\n"; + + if (!IsStageMeta(varname)) { + PUSH_ERROR_AND_RETURN("Unsupported or invalid/empty variable name `" + + varname + "` for Stage metadatum"); + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + if (!Expect('=')) { + PUSH_ERROR_AND_RETURN("`=` expected."); + return false; + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + auto pvardef = GetStageMetaDefinition(varname); + if (!pvardef) { + // This should not happen though; + return false; + } + + auto vardef = (*pvardef); + + MetaVariable var; + if (!ParseMetaValue(vardef, &var)) { + return false; + } + var.set_name(varname); + + std::get<0>(*out) = qual; + std::get<1>(*out) = var; + + return true; +} + +nonstd::optional> +AsciiParser::ParsePrimMeta() { + if (!SkipCommentAndWhitespaceAndNewline()) { + return nonstd::nullopt; + } + + tinyusdz::ListEditQual qual{ListEditQual::ResetToExplicit}; + + // May be string only(varname is "comment") + // For some reason, string-only data is just stored in `MetaVariable` and + // reconstructed in ReconstructPrimMeta in usda-reader.cc later + // + { + value::StringData sdata; + if (MaybeTripleQuotedString(&sdata)) { + MetaVariable var; + // empty name + var.set_value("comment", sdata); + + return std::make_pair(qual, var); + + } else if (MaybeString(&sdata)) { + MetaVariable var; + var.set_value("comment", sdata); + + return std::make_pair(qual, var); + } + } + + if (!MaybeListEditQual(&qual)) { + return nonstd::nullopt; + } + + DCOUT("list-edit qual: " << tinyusdz::to_string(qual)); + + if (!SkipWhitespaceAndNewline()) { + return nonstd::nullopt; + } + + std::string varname; + if (!ReadIdentifier(&varname)) { + return nonstd::nullopt; + } + + DCOUT("Identifier = " << varname); + + bool registered_meta = IsRegisteredPrimMeta(varname); + + if (!Expect('=')) { + PUSH_ERROR("'=' expected in Prim Metadata line."); + return nonstd::nullopt; + } + SkipWhitespace(); + + if (!registered_meta) { + // parse as string until newline + + std::string content; + if (!ReadUntilNewline(&content)) { + PUSH_ERROR("Failed to parse unregistered Prim metadata."); + return nonstd::nullopt; + } + + MetaVariable var; + var.set_value(varname, content); + + return std::make_pair(qual, var); + } else { + + if (auto pv = GetPrimMetaDefinition(varname)) { + MetaVariable var; + const auto vardef = pv.value(); + if (!ParseMetaValue(vardef, &var)) { + PUSH_ERROR("Failed to parse Prim meta value."); + return nonstd::nullopt; + } + var.set_name(varname); + + return std::make_pair(qual, var); + } else { + PUSH_ERROR(fmt::format("[Internal error] Unsupported/unimplemented PrimSpec metadata {}", varname)); + return nonstd::nullopt; + } + } + +} + +bool AsciiParser::ParsePrimMetas( + PrimMetaMap *args) { + // '(' args ')' + // args = list of argument, separated by newline. + + if (!Expect('(')) { + return false; + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + // std::cout << "skip comment/whitespace/nl failed\n"; + DCOUT("SkipCommentAndWhitespaceAndNewline failed."); + return false; + } + + while (!Eof()) { + if (!SkipCommentAndWhitespaceAndNewline()) { + // std::cout << "2: skip comment/whitespace/nl failed\n"; + return false; + } + + char s; + if (!Char1(&s)) { + return false; + } + + if (s == ')') { + DCOUT("Prim meta end"); + // End + break; + } + + Rewind(1); + + DCOUT("Start PrimMeta parse."); + + // ty = std::pair; + if (auto m = ParsePrimMeta()) { + DCOUT("PrimMeta: list-edit qual = " + << tinyusdz::to_string(std::get<0>(m.value())) + << ", name = " << std::get<1>(m.value()).get_name()); + + if (std::get<1>(m.value()).get_name().empty()) { + PUSH_ERROR_AND_RETURN("[InternalError] Metadataum name is empty."); + } + + (*args)[std::get<1>(m.value()).get_name()] = m.value(); + } else { + PUSH_ERROR_AND_RETURN("Failed to parse Meta value."); + } + } + + return true; +} + +bool AsciiParser::ParseAttrMeta(AttrMeta *out_meta) { + // '(' metas ')' + // + // currently we only support 'interpolation', 'elementSize' and 'cutomData' + + if (!SkipWhitespace()) { + return false; + } + + // The first character. + { + char c; + if (!Char1(&c)) { + // this should not happen. + return false; + } + + if (c == '(') { + // ok + } else { + _sr->seek_from_current(-1); + + // Still ok. No meta + DCOUT("No attribute meta."); + return true; + } + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + while (!Eof()) { + char c; + if (!Char1(&c)) { + return false; + } + + if (c == ')') { + // end meta + break; + } else { + if (!Rewind(1)) { + return false; + } + + // May be string only + { + value::StringData sdata; + if (MaybeTripleQuotedString(&sdata)) { + out_meta->stringData.push_back(sdata); + + DCOUT("Add triple-quoted string to attr meta:" << to_string(sdata)); + if (!SkipWhitespaceAndNewline()) { + return false; + } + continue; + } else if (MaybeString(&sdata)) { + out_meta->stringData.push_back(sdata); + + DCOUT("Add string to attr meta:" << to_string(sdata)); + if (!SkipWhitespaceAndNewline()) { + return false; + } + continue; + } + } + + std::string varname; + if (!ReadIdentifier(&varname)) { + return false; + } + + DCOUT("Property/Attribute meta name: " << varname); + + bool supported = _supported_prop_metas.count(varname); + if (!supported) { + PUSH_ERROR_AND_RETURN_TAG(kAscii, + fmt::format("Unsupported Property metadatum name: {}", varname)); + } + + { + std::string name_err; + if (!pathutil::ValidatePropPath(Path("", varname), &name_err)) { + PUSH_ERROR_AND_RETURN_TAG(kAscii, + fmt::format("Invalid Property name `{}`: {}", varname, name_err)); + } + } + + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + if (!Expect('=')) { + return false; + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + // + // First-class predefind prop metas. + // + if (varname == "interpolation") { + std::string value; + if (!ReadStringLiteral(&value)) { + return false; + } + + DCOUT("Got `interpolation` meta : " << value); + out_meta->interpolation = InterpolationFromString(value); + } else if (varname == "elementSize") { + uint32_t value; + if (!ReadBasicType(&value)) { + PUSH_ERROR_AND_RETURN("Failed to parse `elementSize`"); + } + + DCOUT("Got `elementSize` meta : " << value); + out_meta->elementSize = value; + } else if (varname == "colorSpace") { + value::token tok; + if (!ReadBasicType(&tok)) { + PUSH_ERROR_AND_RETURN("Failed to parse `colorSpace`"); + } + // Add as custom meta value. + MetaVariable metavar; + metavar.set_value("colorSpace", tok); + out_meta->meta.emplace("colorSpace", metavar); + } else if (varname == "customData") { + Dictionary dict; + + if (!ParseDict(&dict)) { + return false; + } + + DCOUT("Got `customData` meta"); + out_meta->customData = dict; + + } else if (varname == "weight") { + double value; + if (!ReadBasicType(&value)) { + PUSH_ERROR_AND_RETURN("Failed to parse `weight`"); + } + + DCOUT("Got `weight` meta : " << value); + out_meta->weight = value; + } else if (varname == "bindMaterialAs") { + value::token tok; + if (!ReadBasicType(&tok)) { + PUSH_ERROR_AND_RETURN("Failed to parse `bindMaterialAs`"); + } + if ((tok.str() == kWeaderThanDescendants) || (tok.str() == kStrongerThanDescendants)) { + // ok + } else { + // still valid though + PUSH_WARN("Unsupported token for bindMaterialAs: " << tok.str()); + } + DCOUT("bindMaterialAs: " << tok); + out_meta->bindMaterialAs = tok; + } else if (varname == "displayName") { + std::string str; + if (!ReadStringLiteral(&str)) { + PUSH_ERROR_AND_RETURN("Failed to parse `displayName`(string type)"); + } + DCOUT("displayName: " << str); + out_meta->displayName = str; + + } else if (varname == "connectability") { + value::token tok; + if (!ReadBasicType(&tok)) { + PUSH_ERROR_AND_RETURN("Failed to parse `connectability`"); + } + DCOUT("connectability: " << tok); + out_meta->connectability = tok; + } else if (varname == "renderType") { + value::token tok; + if (!ReadBasicType(&tok)) { + PUSH_ERROR_AND_RETURN("Failed to parse `renderType`"); + } + DCOUT("renderType: " << tok); + out_meta->renderType = tok; + } else if (varname == "outputName") { + value::token tok; + if (!ReadBasicType(&tok)) { + PUSH_ERROR_AND_RETURN("Failed to parse `outputName`"); + } + DCOUT("outputName: " << tok); + out_meta->outputName = tok; + } else if (varname == "sdrMetadata") { + Dictionary dict; + + if (!ParseDict(&dict)) { + return false; + } + + out_meta->sdrMetadata = dict; + } else { + if (auto pv = GetPropMetaDefinition(varname)) { + // Parse as generic metadata variable + MetaVariable metavar; + const auto &vardef = pv.value(); + + if (!ParseMetaValue(vardef, &metavar)) { + return false; + } + metavar.set_name(varname); + + // add to custom meta + out_meta->meta[varname] = metavar; + + } else { + // This should not happen though. + PUSH_ERROR_AND_RETURN_TAG(kAscii, fmt::format("[InternalErrror] Failed to parse Property metadataum `{}`", varname)); + } + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + } + } + + return true; +} + +bool IsUSDA(const std::string &filename, size_t max_filesize) { + // TODO: Read only first N bytes + std::vector data; + std::string err; + + if (!io::ReadWholeFile(&data, &err, filename, max_filesize)) { + return false; + } + + tinyusdz::StreamReader sr(data.data(), data.size(), /* swap endian */ false); + tinyusdz::ascii::AsciiParser parser(&sr); + + return parser.CheckHeader(); +} + +// +// -- Impl +// + +/// +/// Parse `rel` +/// +bool AsciiParser::ParseRelationship(Relationship *result) { + char c; + if (!LookChar1(&c)) { + return false; + } + + if (c == '<') { + // Path + Path value; + if (!ReadBasicType(&value)) { + PUSH_ERROR_AND_RETURN("Failed to parse Path."); + } + + // Resolve relative path here. + // NOTE: Internally, USD(Crate) does not allow relative path. + Path base_prim_path(GetCurrentPrimPath(), ""); + Path abs_path; + std::string err; + if (!pathutil::ResolveRelativePath(base_prim_path, value, &abs_path, &err)) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid relative Path: {}. error = {}", value, err)); + } + + result->set(abs_path); + } else if (c == '[') { + // PathVector + std::vector values; + if (!ParseBasicTypeArray(&values)) { + PUSH_ERROR_AND_RETURN("Failed to parse PathVector."); + } + + // Resolve relative path here. + // NOTE: Internally, USD(Crate) does not allow relative path. + for (size_t i = 0; i < values.size(); i++) { + Path base_prim_path(GetCurrentPrimPath(), ""); + Path abs_path; + if (!pathutil::ResolveRelativePath(base_prim_path, values[i], &abs_path)) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid relative Path: {}.", values[i].full_path_name())); + } + + // replace + values[i] = abs_path; + } + + result->set(values); + } else if (c == 'N') { + // None + nonstd::optional value; + if (!ReadBasicType(&value)) { + PUSH_ERROR_AND_RETURN("Failed to parse None."); + } + + // Should be empty for None. + if (value.has_value()) { + PUSH_ERROR_AND_RETURN("Failed to parse None."); + } + + result->set_blocked(); + } else { + PUSH_ERROR_AND_RETURN("Unexpected char \"" + std::to_string(c) + + "\" found. Expects Path or PathVector."); + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + return true; +} + +template +bool AsciiParser::ParseBasicPrimAttr(bool array_qual, + const std::string &primattr_name, + Attribute *out_attr) { + Attribute attr; + primvar::PrimVar var; + bool blocked{false}; + + if (array_qual) { + if (MaybeNone()) { + } else { + std::vector value; + if (!ParseBasicTypeArray(&value)) { + PUSH_ERROR_AND_RETURN("Failed to parse " + + std::string(value::TypeTraits::type_name()) + + " array."); + } + + // Empty array allowed. + DCOUT("Got it: ty = " + std::string(value::TypeTraits::type_name()) + + ", sz = " + std::to_string(value.size())); + var.set_value(value); + } + + } else if (hasConnect(primattr_name)) { + std::string value; // TODO: Use Path + if (!ReadPathIdentifier(&value)) { + PUSH_ERROR_AND_RETURN("Failed to parse path identifier."); + } + + // validate. + Path connectionPath = pathutil::FromString(value); + if (!connectionPath.is_valid()) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid connectionPath: {}.", value)); + } + + // Resolve relative path here. + // NOTE: Internally, USD(Crate) does not allow relative path. + Path base_prim_path(GetCurrentPrimPath(), ""); + Path abs_path; + if (!pathutil::ResolveRelativePath(base_prim_path, connectionPath, &abs_path)) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid relative Path: {}.", value)); + } + + // TODO: Use Path + var.set_value(abs_path.full_path_name()); + } else { + nonstd::optional value; + if (!ReadBasicType(&value)) { + PUSH_ERROR_AND_RETURN("Failed to parse " + + std::string(value::TypeTraits::type_name())); + } + + if (value) { + DCOUT("ParseBasicPrimAttr: " << value::TypeTraits::type_name() << " = " + << (*value)); + + var.set_value(value.value()); + + } else { + blocked = true; + // std::cout << "ParseBasicPrimAttr: " << + // value::TypeTraits::type_name() + // << " = None\n"; + } + } + + // optional: attribute meta. + AttrMeta meta; + if (!ParseAttrMeta(&meta)) { + PUSH_ERROR_AND_RETURN("Failed to parse Attribute meta."); + } + attr.metas() = meta; + + if (blocked) { + // There is still have a type for ValueBlock. + value::ValueBlock noneval; + attr.set_value(noneval); + attr.set_blocked(true); + if (array_qual) { + attr.set_type_name(value::TypeTraits::type_name() + "[]"); + } else { + attr.set_type_name(value::TypeTraits::type_name()); + } + } else { + attr.set_var(std::move(var)); + } + + (*out_attr) = std::move(attr); + + return true; +} + +bool AsciiParser::ParsePrimProps(std::map *props, std::vector *propNames) { + + (void)propNames; + + // prim_prop : (custom?) (variability?) type (array_qual?) name '=' value + // | (custom?) type (array_qual?) name '=' value interpolation? + // | (custom?) (variability?) type (array_qual?) name interpolation? + // | (custom?) (listeditqual?) (variability?) rel attr_name = None + // | (custom?) (listeditqual?) (variability?) rel attr_name = string meta + // | (custom?) (listeditqual?) (variability?) rel attr_name = path meta + // | (custom?) (listeditqual?) (variability?) rel attr_name = pathvector meta + // | (custom?) (listeditqual?) (variability?) rel attr_name meta + // ; + + // NOTE: + // custom append varying ... is not allowed. + // append varying custom ... is not allowed. + // append custom varying ... is allowed(decomposed into `custom varying ...` and `append varying ...` + + // Skip comment + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + // Parse `custom` + bool custom_qual = MaybeCustom(); + + if (!SkipWhitespace()) { + return false; + } + + ListEditQual listop_qual; + if (!MaybeListEditQual(&listop_qual)) { + return false; + } + + // `custom` then listop is not allowed. + if (listop_qual != ListEditQual::ResetToExplicit) { + if (custom_qual) { + PUSH_ERROR_AND_RETURN("`custom` then ListEdit qualifier is not allowed."); + } + + // listop then `custom` is allowed. + custom_qual = MaybeCustom(); + } + + bool varying_authored{false}; + tinyusdz::Variability variability{tinyusdz::Variability::Varying}; + + if (!MaybeVariability(&variability, &varying_authored)) { + return false; + } + DCOUT("variability = " << to_string(variability) << ", varying_authored " << varying_authored); + + std::string type_name; + + if (!ReadIdentifier(&type_name)) { + return false; + } + + if (!SkipWhitespace()) { + return false; + } + + DCOUT("type_name = " << type_name); + + // `uniform` or `varying` + + // Relation('rel') + if (type_name == kRel) { + DCOUT("relation"); + + if (variability == Variability::Uniform) { + PUSH_ERROR_AND_RETURN( + "Explicit `uniform` variability keyword is not allowed for Relationship."); + } + + // - prim_identifier + // - prim_identifier, '(' metadataum ')' + // - prim_identifier, '=', (None|string|path|pathvector) + // NOTE: There should be no 'uniform rel' + + std::string attr_name; + + if (!ReadPrimAttrIdentifier(&attr_name)) { + PUSH_ERROR_AND_RETURN( + "Attribute name(Identifier) expected but got non-identifier."); + } + + if (!SkipWhitespace()) { + return false; + } + + char c; + if (!LookChar1(&c)) { + return false; + } + + nonstd::optional metap; + + if (c == '(') { + // FIXME: Implement Relation specific metadatum parser? + AttrMeta meta; + if (!ParseAttrMeta(&meta)) { + PUSH_ERROR_AND_RETURN("Failed to parse metadataum."); + } + + metap = meta; + + if (!LookChar1(&c)) { + return false; + } + + } + + if (c != '=') { + DCOUT("Relationship with no target: " << attr_name); + + // No targets. Define only. + Property p; + p.set_property_type(Property::Type::NoTargetsRelation); + p.set_listedit_qual(listop_qual); + + if (varying_authored) { + p.relationship().set_varying_authored(); + } + + if (metap) { + // TODO: metadataum for Rel + p.relationship().metas() = metap.value(); + } + + (*props)[attr_name] = p; + + return true; + } + + // has targets + if (!Expect('=')) { + return false; + } + + if (metap) { + PUSH_ERROR_AND_RETURN_TAG(kAscii, "Syntax error. Property metadatum must be defined after `=` and relationship target(s)."); + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + Relationship rel; + if (!ParseRelationship(&rel)) { + PUSH_ERROR_AND_RETURN("Failed to parse `rel` property."); + } + + if (!SkipWhitespace()) { + } + + if (!LookChar1(&c)) { + return false; + } + + if (c == '(') { + + if (metap) { + PUSH_ERROR_AND_RETURN_TAG(kAscii, "[InternalError] parser error."); + } + + AttrMeta meta; + + // FIXME: Implement Relation specific metadatum parser? + if (!ParseAttrMeta(&meta)) { + PUSH_ERROR_AND_RETURN("Failed to parse metadataum."); + } + + metap = meta; + + } + + DCOUT("Relationship with target: " << attr_name); + Property p(rel, custom_qual); + p.set_listedit_qual(listop_qual); + + if (varying_authored) { + p.relationship().set_varying_authored(); + } + + if (metap) { + p.relationship().metas() = metap.value(); + } + + (*props)[attr_name] = p; + + return true; + } + + // + // Attrib. + // + + if (listop_qual != ListEditQual::ResetToExplicit) { + PUSH_ERROR_AND_RETURN_TAG( + kAscii, "List editing qualifier is not allowed for Attribute."); + } + + if (!IsSupportedPrimAttrType(type_name)) { + PUSH_ERROR_AND_RETURN("Unknown or unsupported primtive attribute type `" + + type_name); + } + + // Has array qualifier? `[]` + bool array_qual = false; + { + char c0, c1; + if (!Char1(&c0)) { + return false; + } + + if (c0 == '[') { + if (!Char1(&c1)) { + return false; + } + + if (c1 == ']') { + array_qual = true; + } else { + // Invalid syntax + PUSH_ERROR_AND_RETURN("Invalid syntax found."); + } + + } else { + if (!Rewind(1)) { + return false; + } + } + } + + if (!SkipWhitespace()) { + return false; + } + + std::string primattr_name; + if (!ReadPrimAttrIdentifier(&primattr_name)) { + PUSH_ERROR_AND_RETURN("Failed to parse primAttr identifier."); + } + + if (!SkipWhitespace()) { + return false; + } + + bool isTimeSample = endsWith(primattr_name, kTimeSamplesSuffix); + bool isConnection = endsWith(primattr_name, kConnectSuffix); + + // Remove suffix + std::string attr_name = primattr_name; + if (isTimeSample) { + attr_name = removeSuffix(primattr_name, kTimeSamplesSuffix); + } + if (isConnection) { + attr_name = removeSuffix(primattr_name, kConnectSuffix); + } + + bool define_only = false; + { + char c; + if (!Char1(&c)) { + return false; + } + + if (c != '=') { + // Define only(e.g. output variable) + define_only = true; + } + } + + DCOUT("define only:" << define_only); + + if (define_only) { + + Rewind(1); + + // optional: attribute meta. + AttrMeta meta; + if (!ParseAttrMeta(&meta)) { + PUSH_ERROR_AND_RETURN("Failed to parse Attribute meta."); + } + + DCOUT("Define only property = " + primattr_name); + + // Empty Attribute. type info only + Property p; + p.set_property_type(Property::Type::EmptyAttrib); + p.set_custom(custom_qual); + std::string typeName = type_name; + if (array_qual) { + typeName += "[]"; + } + p.attribute().set_type_name(typeName); + + p.attribute().variability() = variability; + if (varying_authored) { + p.attribute().set_varying_authored(); + } + + p.attribute().metas() = meta; + + (*props)[attr_name] = p; + + return true; + } + + // Continue to parse argument + if (!SkipWhitespace()) { + return false; + } + + bool value_blocked{false}; + + if (MaybeNone()) { + value_blocked = true; + } + + if (isConnection) { + // atribute connection + DCOUT("isConnection"); + + Path path; + if (!value_blocked) { + // Target Must be Path + if (!ReadBasicType(&path)) { + PUSH_ERROR_AND_RETURN("Path expected for .connect target."); + } + } + + // Resolve relative path. + Path base_abs_path(GetCurrentPrimPath(), ""); + Path abs_path; + std::string err; + if (!pathutil::ResolveRelativePath(base_abs_path, path, &abs_path, &err)) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid relative Path: {}. error = {}", path.full_path_name(), err)); + } + + Property p(abs_path, /* value typename */ type_name, custom_qual); + if (value_blocked) { + p.attribute().set_blocked(true); + } + + p.attribute().variability() = variability; + if (varying_authored) { + p.attribute().set_varying_authored(); + } + + (*props)[attr_name] = p; + + DCOUT(fmt::format("Added {} as a attribute connection.", primattr_name)); + + return true; + + } else if (isTimeSample) { + // float.timeSamples = None is not supported + if (value_blocked) { + PUSH_ERROR_AND_RETURN("ValueBlock to .timeSamples is not supported."); + } + + // + // TODO(syoyo): Refactror and implement value parser dispatcher. + // + if (array_qual) { + DCOUT("timeSample data. type = " << type_name << "[]"); + } else { + DCOUT("timeSample data. type = " << type_name); + } + + value::TimeSamples ts; + if (array_qual) { + if (!ParseTimeSamplesOfArray(type_name, &ts)) { + PUSH_ERROR_AND_RETURN_TAG(kAscii, fmt::format("Failed to parse TimeSamples of type {}[]", type_name)); + } + } else { + if (!ParseTimeSamples(type_name, &ts)) { + PUSH_ERROR_AND_RETURN_TAG(kAscii, fmt::format("Failed to parse TimeSamples of type {}", type_name)); + } + } + + //std::string varname = removeSuffix(primattr_name, ".timeSamples"); + Attribute attr; + primvar::PrimVar var; + var.set_timesamples(ts); + + attr.name() = attr_name; + attr.set_var(std::move(var)); + + attr.variability() = variability; + if (varying_authored) { + attr.set_varying_authored(); + } + + DCOUT("timeSamples primattr: type = " << type_name + << ", name = " << attr_name); + + Property p(attr, custom_qual); + p.set_property_type(Property::Type::Attrib); + (*props)[attr_name] = p; + + return true; + + } else { + Attribute attr; + if (!value_blocked) { + + // TODO: Refactor. ParseAttrMeta is currently called inside + // ParseBasicPrimAttr() + if (type_name == value::kBool) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kInt) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kInt2) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kInt3) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kInt4) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kUInt) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kUInt2) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kUInt3) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kUInt4) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kInt64) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kUInt64) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kDouble) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kString) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kToken) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kHalf) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kHalf2) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kHalf3) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kHalf4) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kFloat) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, &attr)) { + return false; + } + } else if (type_name == value::kFloat2) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kFloat3) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kFloat4) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kDouble2) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kDouble3) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kDouble4) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kQuath) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kQuatf) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kQuatd) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kPoint3f) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kColor3f) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kColor4f) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kPoint3d) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kNormal3f) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kNormal3d) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kVector3f) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kVector3d) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kColor3d) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kColor4d) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kMatrix2f) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kMatrix3f) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kMatrix4f) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kMatrix2d) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kFloat3) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kFloat4) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kDouble2) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kDouble3) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kDouble4) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kPoint3f) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kColor3f) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kColor4f) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kPoint3d) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kNormal3f) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kNormal3d) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kVector3f) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kVector3d) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kColor3d) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kColor4d) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kMatrix2f) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kMatrix3f) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kMatrix4f) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + + } else if (type_name == value::kMatrix2d) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kMatrix3d) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else if (type_name == value::kMatrix4d) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + + } else if (type_name == value::kTexCoord2f) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + + } else if (type_name == value::kAssetPath) { + if (!ParseBasicPrimAttr(array_qual, primattr_name, + &attr)) { + return false; + } + } else { + PUSH_ERROR_AND_RETURN("TODO: type = " + type_name); + } + } + + attr.variability() = variability; + if (varying_authored) { + attr.set_varying_authored(); + } + + if (value_blocked) { + if (array_qual) { + attr.set_type_name(type_name + "[]"); + } else { + attr.set_type_name(type_name); + } + attr.set_blocked(true); + } + + attr.set_name(primattr_name); + + DCOUT("primattr: type = " << type_name << ", name = " << primattr_name); + + Property p(attr, custom_qual); + + (*props)[primattr_name] = p; + + return true; + } +} + +// propNames stores list of property name in its appearance order. +bool AsciiParser::ParseProperties(std::map *props, std::vector *propNames) { + // property : primm_attr + // | 'rel' name '=' path + // ; + + if (!SkipWhitespace()) { + return false; + } + + // rel? + { + uint64_t loc = CurrLoc(); + std::string tok; + + if (!ReadIdentifier(&tok)) { + return false; + } + + if (tok == "rel") { + PUSH_ERROR_AND_RETURN("TODO: Parse rel"); + } else { + SeekTo(loc); + } + } + + // attribute + return ParsePrimProps(props, propNames); +} + +std::string AsciiParser::GetCurrentPrimPath() { + if (_path_stack.empty()) { + return "/"; + } + + return _path_stack.top(); +} + +// +// -- ctor, dtor +// + +AsciiParser::AsciiParser() { Setup(); } + +AsciiParser::AsciiParser(StreamReader *sr) : _sr(sr) { Setup(); } + +void AsciiParser::Setup() { + RegisterStageMetas(_supported_stage_metas); + RegisterPrimMetas(_supported_prim_metas); + RegisterPropMetas(_supported_prop_metas); + RegisterPrimAttrTypes(_supported_prim_attr_types); + RegisterPrimTypes(_supported_prim_types); + RegisterAPISchemas(_supported_api_schemas); +} + +AsciiParser::~AsciiParser() {} + +bool AsciiParser::CheckHeader() { return ParseMagicHeader(); } + +bool AsciiParser::IsRegisteredPrimMeta(const std::string &name) { + return _supported_prim_metas.count(name) ? true : false; +} + +bool AsciiParser::IsStageMeta(const std::string &name) { + return _supported_stage_metas.count(name) ? true : false; +} + +bool AsciiParser::ParseVariantSet(const int64_t primIdx, + const int64_t parentPrimIdx, + const uint32_t depth, + std::map *variantSetOut) { + + if (!variantSetOut) { + PUSH_ERROR_AND_RETURN_TAG(kAscii, "[InternalError] variantSetOut arg is nullptr."); + } + + // variantSet = + // { + // "variantName0" ( metas ) { ... } + // "variantName1" ( metas ) { ... } + // ... + // } + if (!Expect('{')) { + return false; + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + std::map variantContentMap; + + // for each variantStatement + while (!Eof()) { + { + char c; + if (!Char1(&c)) { + return false; + } + + if (c == '}') { + // end + break; + } + + Rewind(1); + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + // string + std::string variantName; + if (!ReadBasicType(&variantName)) { + PUSH_ERROR_AND_RETURN_TAG( + kAscii, "Failed to parse variant name for `variantSet` statement."); + } + + if (!SkipWhitespace()) { + return false; + } + + // Optional: PrimSpec meta + PrimMetaMap metas; + { + char mc; + if (!LookChar1(&mc)) { + return false; + } + + if (mc == '(') { + if (!ParsePrimMetas(&metas)) { + PUSH_ERROR_AND_RETURN_TAG(kAscii, "Failed to parse PrimSpec metas in variant statement."); + } + } + } + + if (!Expect('{')) { + return false; + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + VariantContent variantContent; + + while (!Eof()) { + { + char c; + if (!Char1(&c)) { + return false; + } + + if (c == '}') { + DCOUT("End block in variantSet stmt."); + // end block + break; + } + } + + if (!Rewind(1)) { + return false; + } + + DCOUT("Read first token in VariantSet stmt"); + Identifier tok; + if (!ReadBasicType(&tok)) { + PUSH_ERROR_AND_RETURN( + "Failed to parse an identifier in variantSet block statement."); + } + + if (!Rewind(tok.size())) { + return false; + } + + if (tok == "variantSet") { + PUSH_ERROR_AND_RETURN("Nested `variantSet` is not supported yet."); + } + + Specifier child_spec{Specifier::Invalid}; + if (tok == "def") { + child_spec = Specifier::Def; + } else if (tok == "over") { + child_spec = Specifier::Over; + } else if (tok == "class") { + child_spec = Specifier::Class; + } + + // No specifier => Assume properties only. + // Has specifier => Prim + if (child_spec != Specifier::Invalid) { + // FIXME: Assign idx dedicated for variant. + int64_t idx = _prim_idx_assign_fun(parentPrimIdx); + DCOUT("enter parseBlock in variantSet. spec = " << to_string(child_spec) << ", idx = " + << idx << ", rootIdx = " << primIdx); + + // recusive call + if (!ParseBlock(child_spec, idx, primIdx, depth + 1, /* in_variantStmt */true)) { + PUSH_ERROR_AND_RETURN( + fmt::format("`{}` block parse failed.", to_string(child_spec))); + } + DCOUT(fmt::format("Done parse `{}` block.", to_string(child_spec))); + + DCOUT(fmt::format("Add primIdx {} to variant {}", idx, variantName)); + variantContent.primIndices.push_back(idx); + + } else { + DCOUT("Enter ParsePrimProps."); + if (!ParsePrimProps(&variantContent.props, &variantContent.properties)) { + PUSH_ERROR_AND_RETURN("Failed to parse Prim attribute."); + } + DCOUT(fmt::format("Done parse ParsePrimProps.")); + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + DCOUT(fmt::format("variantSet item {} parsed.", variantName)); + + variantContent.metas = metas; + variantContentMap.emplace(variantName, variantContent); + } + + (*variantSetOut) = std::move(variantContentMap); + + return true; +} + +/// +/// Parse block. +/// +/// block = spec prim_type? token metas? { ... } +/// metas = '(' args ')' +/// +/// spec = `def`, `over` or `class` +/// +/// +bool AsciiParser::ParseBlock(const Specifier spec, const int64_t primIdx, + const int64_t parentPrimIdx, + const uint32_t depth, + const bool in_variantStaement) { + (void)in_variantStaement; + + DCOUT("ParseBlock"); + + if (!SkipCommentAndWhitespaceAndNewline()) { + DCOUT("SkipCommentAndWhitespaceAndNewline failed"); + return false; + } + + Identifier def; + if (!ReadIdentifier(&def)) { + DCOUT("ReadIdentifier failed"); + return false; + } + DCOUT("spec = " << def); + + if ((def == "def") || (def == "over") || (def == "class")) { + // ok + } else { + PUSH_ERROR_AND_RETURN("Invalid specifier."); + } + + // Ensure spec and def is same. + if (def == "def") { + if (spec != Specifier::Def) { + PUSH_ERROR_AND_RETURN_TAG( + kAscii, "Internal error. Invalid Specifier token combination. def = " << def << ", spec = " << to_string(spec)); + } + } else if (def == "over") { + if (spec != Specifier::Over) { + PUSH_ERROR_AND_RETURN_TAG( + kAscii, "Internal error. Invalid Specifier token combination. def = " << def << ", spec = " << to_string(spec)); + } + } else if (def == "class") { + if (spec != Specifier::Class) { + PUSH_ERROR_AND_RETURN_TAG( + kAscii, "Internal error. Invalid Specifier token combination. def = " << def << ", spec = " << to_string(spec)); + } + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + // look ahead + bool has_primtype = false; + { + char c; + if (!Char1(&c)) { + return false; + } + + if (!Rewind(1)) { + return false; + } + + if (c == '"') { + // token + has_primtype = false; + } else { + has_primtype = true; + } + } + + Identifier prim_type; + + DCOUT("has_primtype = " << has_primtype); + + if (has_primtype) { + if (!ReadIdentifier(&prim_type)) { + return false; + } + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + std::string prim_name; + if (!ReadBasicType(&prim_name)) { + return false; + } + + DCOUT("prim name = " << prim_name); + if (!ValidatePrimElementName(prim_name)) { + PUSH_ERROR_AND_RETURN_TAG(kAscii, "Prim name contains invalid chacracter."); + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + std::map> in_metas; + { + // look ahead + char c; + if (!LookChar1(&c)) { + return false; + } + + if (c == '(') { + // meta + + if (!ParsePrimMetas(&in_metas)) { + DCOUT("Parse Prim metas failed."); + return false; + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + } + } + + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + if (!Expect('{')) { + return false; + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + + std::map props; + std::vector propNames; + VariantSetList variantSetList; + + { + std::string full_path = GetCurrentPrimPath(); + if (full_path == "/") { + full_path += prim_name; + } else { + full_path += "/" + prim_name; + } + PushPrimPath(full_path); + } + + // expect = '}' + // | def_block + // | prim_attr+ + // | variantSet '{' ... '}' + while (!Eof()) { + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + char c; + if (!Char1(&c)) { + return false; + } + + if (c == '}') { + // end block + break; + } else { + if (!Rewind(1)) { + return false; + } + + DCOUT("Read stmt token"); + Identifier tok; + if (!ReadBasicType(&tok)) { + // maybe ';'? + + if (LookChar1(&c)) { + if (c == ';') { + PUSH_ERROR_AND_RETURN( + "Semicolon is not allowd in `def` block statement."); + } + } + PUSH_ERROR_AND_RETURN( + "Failed to parse an identifier in `def` block statement."); + } + + if (tok == "variantSet") { + if (!SkipWhitespace()) { + return false; + } + + std::string variantName; + if (!ReadBasicType(&variantName)) { + PUSH_ERROR_AND_RETURN("Failed to parse `variantSet` statement."); + } + + DCOUT("variantName = " << variantName); + + if (!SkipWhitespace()) { + return false; + } + + if (!Expect('=')) { + return false; + } + + if (!SkipWhitespace()) { + return false; + } + + std::map vmap; + if (!ParseVariantSet(primIdx, parentPrimIdx, depth, &vmap)) { + PUSH_ERROR_AND_RETURN("Failed to parse `variantSet` statement."); + } + + variantSetList.emplace(variantName, vmap); + + continue; + } + + if (!Rewind(tok.size())) { + return false; + } + + Specifier child_spec{Specifier::Invalid}; + if (tok == "def") { + child_spec = Specifier::Def; + } else if (tok == "over") { + child_spec = Specifier::Over; + } else if (tok == "class") { + child_spec = Specifier::Class; + } + + if (child_spec != Specifier::Invalid) { + int64_t idx = _prim_idx_assign_fun(parentPrimIdx); + DCOUT("enter parseDef. spec = " << to_string(child_spec) << ", idx = " + << idx << ", rootIdx = " << primIdx); + + // recusive call + if (!ParseBlock(child_spec, idx, primIdx, depth + 1)) { + PUSH_ERROR_AND_RETURN( + fmt::format("`{}` block parse failed.", to_string(child_spec))); + } + DCOUT(fmt::format("Done parse `{}` block.", to_string(child_spec))); + } else { + DCOUT("Enter ParsePrimProps."); + // Assume PrimAttr + if (!ParsePrimProps(&props, &propNames)) { + PUSH_ERROR_AND_RETURN("Failed to parse Prim attribute."); + } + } + + if (!SkipWhitespaceAndNewline()) { + return false; + } + } + } + + std::string pTy = prim_type; + + if (_primspec_mode) { + // Load scene as PrimSpec tree + if (_primspec_fun) { + Path fullpath(GetCurrentPrimPath(), ""); + Path pname(prim_name, ""); + + // pass prim_type as is(empty = empty string) + nonstd::expected ret = _primspec_fun( + fullpath, spec, prim_type, pname, primIdx, parentPrimIdx, props, in_metas, variantSetList); + + if (!ret) { + // construction failed. + PUSH_ERROR_AND_RETURN(fmt::format("Constructing PrimSpec typeName `{}`, elementName `{}` failed: {}", prim_type, prim_name, ret.error())); + } + } else { + PUSH_ERROR_AND_RETURN_TAG(kAscii, "[Internal Error] PrimSpec handler is not found."); + } + + } else { + + // Create typed Prim. + + if (prim_type.empty()) { + // No Prim type specified. Treat it as Model + + pTy = "Model"; + } + + if (!_prim_construct_fun_map.count(pTy)) { + if (_option.allow_unknown_prim) { + // Unknown Prim type specified. Treat it as Model + // Prim's type name will be storead in Model::prim_type_name + pTy = "Model"; + } + } + + if (_prim_construct_fun_map.count(pTy)) { + auto construct_fun = _prim_construct_fun_map[pTy]; + + Path fullpath(GetCurrentPrimPath(), ""); + Path pname(prim_name, ""); + nonstd::expected ret = construct_fun( + fullpath, spec, prim_type, pname, primIdx, parentPrimIdx, props, in_metas, variantSetList); + + if (!ret) { + // construction failed. + PUSH_ERROR_AND_RETURN("Constructing Prim type `" + pTy + + "` failed: " + ret.error()); + } + + } else { + PUSH_WARN(fmt::format( + "TODO: Unsupported/Unimplemented Prim type: `{}`. Skipping parsing.", + pTy)); + } + + + } + + PopPrimPath(); + + return true; +} + +/// +/// Parser entry point +/// TODO: Refactor and use unified code path regardless of LoadState. +/// +bool AsciiParser::Parse(const uint32_t load_states, const AsciiParserOption &parser_option) { + _toplevel = (load_states & static_cast(LoadState::Toplevel)); + _sub_layered = (load_states & static_cast(LoadState::Sublayer)); + _referenced = (load_states & static_cast(LoadState::Reference)); + _payloaded = (load_states & static_cast(LoadState::Payload)); + _option = parser_option; + + bool header_ok = ParseMagicHeader(); + if (!header_ok) { + PUSH_ERROR_AND_RETURN("Failed to parse USDA magic header.\n"); + } + + SkipCommentAndWhitespaceAndNewline(); + + if (Eof()) { + // Empty USDA + return true; + } + + { + char c; + if (!LookChar1(&c)) { + return false; + } + + if (c == '(') { + // stage meta. + if (!ParseStageMetas()) { + PUSH_ERROR_AND_RETURN("Failed to parse Stage metas."); + } + } + } + + if (_stage_meta_process_fun) { + DCOUT("Invoke StageMeta callback."); + bool ret = _stage_meta_process_fun(_stage_metas); + if (!ret) { + PUSH_ERROR_AND_RETURN("Failed to reconstruct Stage metas."); + } + } else { + // TODO: Report error when StageMeta callback is not set? + PUSH_WARN("Stage metadata processing callback is not set."); + } + + PushPrimPath("/"); + + // parse blocks + while (!Eof()) { + if (!SkipCommentAndWhitespaceAndNewline()) { + return false; + } + + if (Eof()) { + // Whitespaces in the end of line. + break; + } + + // Look ahead token + auto curr_loc = _sr->tell(); + + Identifier tok; + if (!ReadBasicType(&tok)) { + PUSH_ERROR_AND_RETURN("Identifier expected.\n"); + } + + // Rewind + if (!SeekTo(curr_loc)) { + return false; + } + + Specifier spec{Specifier::Invalid}; + if (tok == "def") { + spec = Specifier::Def; + } else if (tok == "over") { + spec = Specifier::Over; + } else if (tok == "class") { + spec = Specifier::Class; + } else { + PUSH_ERROR_AND_RETURN("Invalid specifier token '" + tok + "'"); + } + + int64_t primIdx = _prim_idx_assign_fun(-1); + DCOUT("Enter parseDef. primIdx = " << primIdx + << ", parentPrimIdx = root(-1)"); + bool block_ok = ParseBlock(spec, primIdx, /* parent */ -1, /* depth */0, /* in_variantStmt */false); + if (!block_ok) { + PUSH_ERROR_AND_RETURN("Failed to parse `def` block."); + } + } + + return true; +} + +bool ParseUnregistredValue(const std::string &_typeName, const std::string &str, value::Value *value, std::string *err) { + if (!value) { + if (err) { + (*err) += "`value` argument is nullptr.\n"; + } + return false; + } + + bool array_qual = false; + std::string typeName = _typeName; + if (endsWith(typeName, "[]")) { + typeName = removeSuffix(typeName, "[]"); + array_qual = true; + } + + nonstd::optional typeId = value::TryGetTypeId(typeName); + + if (!typeId) { + if (err) { + (*err) += "Unsupported type: " + typeName + "\n"; + } + return false; + } + + tinyusdz::StreamReader sr(reinterpret_cast(str.data()), str.size(), /* swap endian */ false); + tinyusdz::ascii::AsciiParser parser(&sr); + +#define PARSE_BASE_TYPE(__ty) case value::TypeTraits<__ty>::type_id(): { \ + if (array_qual) { \ + std::vector<__ty> vss; \ + if (!parser.ParseBasicTypeArray(&vss)) { \ + if (err) { \ + (*err) = fmt::format("Failed to parse a value of type `{}[]`", value::TypeTraits<__ty>::type_name()); \ + } \ + return false; \ + } \ + dst = vss; \ + } else { \ + __ty val; \ + if (!parser.ReadBasicType(&val)) { \ + if (err) { \ + (*err) = fmt::format("Failed to parse a value of type `{}`", value::TypeTraits<__ty>::type_name()); \ + } \ + return false; \ + } \ + dst = val; \ + } \ + break; \ + } + + value::Value dst; + + + switch (typeId.value()) { + PARSE_BASE_TYPE(value::uint2) + PARSE_BASE_TYPE(value::uint3) + PARSE_BASE_TYPE(value::uint4) + default: { + if (err) { + (*err) = fmt::format("Unsupported or unimplemeneted type `{}`", typeName); + } + return false; + } + } + + (*value) = std::move(dst); + + return true; +} + +} // namespace ascii +} // namespace tinyusdz + +#else // TINYUSDZ_DISABLE_MODULE_USDA_READER + +bool ParseUnregistredValue(const std::string &typeName, const std::string &str, value::Value *value, std::string *err) { + if (err) { + (*err) += "USDA_READER module is disabled.\n"; + } + return false; +} + +#endif // TINYUSDZ_DISABLE_MODULE_USDA_READER diff --git a/contrib/tinyusdz/tinyusdz_repo/src/ascii-parser.hh b/contrib/tinyusdz/tinyusdz_repo/src/ascii-parser.hh new file mode 100644 index 000000000..1daa07203 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/ascii-parser.hh @@ -0,0 +1,893 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2021 - 2022, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +// USD ASCII parser + +#pragma once + +// #include +#include + +#include + +// #include "external/better-enums/enum.h" +#include "composition.hh" +#include "prim-types.hh" +#include "stream-reader.hh" +#include "tinyusdz.hh" + +// +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +// external +#include "nonstd/expected.hpp" + +// +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace tinyusdz { + +namespace ascii { + +// keywords +constexpr auto kUniform = "uniform"; +constexpr auto kToken = "token"; + +// Frequently used attr/meta keywords +constexpr auto kKind = "kind"; +constexpr auto kInterpolation = "interpolation"; + +struct Identifier : std::string { + // using std::string; +}; + +// FIXME: Not used? remove. +struct PathIdentifier : std::string { + // using std::string; +}; + +// Parser option. +// For strict configuration(e.g. read USDZ on Mobile), should disallow unknown +// items. +struct AsciiParserOption { + bool allow_unknown_prim{true}; + bool allow_unknown_apiSchema{true}; + bool strict_allowedToken_check{false}; +}; + +/// +/// Test if input file is USDA ascii format. +/// +bool IsUSDA(const std::string &filename, size_t max_filesize = 0); + +class AsciiParser { + public: + // TODO: refactor + struct PrimMetas { + // Frequently used prim metas + nonstd::optional kind; + + value::dict customData; // `customData` + std::vector + strings; // String only unregistered metadata. + }; + + // TODO: Unifity class with StageMetas in prim-types.hh + struct StageMetas { + /// + /// Predefined Stage metas + /// + std::vector subLayers; // 'subLayers' + value::token defaultPrim; // 'defaultPrim' + value::StringData doc; // 'doc' or 'documentation' + nonstd::optional upAxis; // not specified = nullopt + nonstd::optional metersPerUnit; + nonstd::optional timeCodesPerSecond; + nonstd::optional startTimeCode; + nonstd::optional endTimeCode; + nonstd::optional framesPerSecond; + + nonstd::optional autoPlay; + nonstd::optional playbackMode; // 'none' or 'loop' + + std::map customLayerData; // `customLayerData`. + value::StringData comment; // String only comment string. + }; + + struct ParseState { + int64_t loc{-1}; // byte location in StreamReder + }; + + struct Cursor { + int row{0}; + int col{0}; + }; + + struct ErrorDiagnostic { + std::string err; + Cursor cursor; + }; + + void PushError(const std::string &msg) { + ErrorDiagnostic diag; + diag.cursor.row = _curr_cursor.row; + diag.cursor.col = _curr_cursor.col; + diag.err = msg; + err_stack.push(diag); + } + + // This function is used to cancel recent parsing error. + void PopError() { + if (!err_stack.empty()) { + err_stack.pop(); + } + } + + void PushWarn(const std::string &msg) { + ErrorDiagnostic diag; + diag.cursor.row = _curr_cursor.row; + diag.cursor.col = _curr_cursor.col; + diag.err = msg; + warn_stack.push(diag); + } + + // This function is used to cancel recent parsing warning. + void PopWarn() { + if (!warn_stack.empty()) { + warn_stack.pop(); + } + } + + bool IsStageMeta(const std::string &name); + bool IsRegisteredPrimMeta(const std::string &name); + + class VariableDef { + public: + // Handler functor in post parsing stage. + // e.g. Check input string is a valid one: one of "common", "group", + // "assembly", "component" or "subcomponent" for "kind" metadata + using PostParseHandler = + std::function(const std::string &)>; + + static nonstd::expected DefaultPostParseHandler( + const std::string &) { + return true; + } + + std::string type; // e.g. token, color3f + std::string name; + bool allow_array_type{false}; // when true, we accept `type` and `type[]` + + PostParseHandler post_parse_handler; + + VariableDef() = default; + + VariableDef(const std::string &t, const std::string &n, bool a = false, + PostParseHandler ph = DefaultPostParseHandler) + : type(t), name(n), allow_array_type(a), post_parse_handler(ph) {} + + VariableDef(const VariableDef &rhs) = default; + VariableDef &operator=(const VariableDef &rhs) = default; + + // VariableDef &operator=(const VariableDef &rhs) { + // type = rhs.type; + // name = rhs.name; + // parse_handler = rhs.parse_handler; + + // return *this; + //} + }; + + using PrimMetaMap = + std::map>; + + struct VariantContent { + PrimMetaMap metas; + std::vector primIndices; // primIdx of Reconstrcuted Prim. + std::map props; + std::vector properties; + + // for nested `variantSet` + std::map> variantSets; + }; + + // TODO: Use std::vector instead of std::map? + using VariantSetList = + std::map>; + + AsciiParser(); + AsciiParser(tinyusdz::StreamReader *sr); + + AsciiParser(const AsciiParser &rhs) = delete; + AsciiParser(AsciiParser &&rhs) = delete; + + ~AsciiParser(); + + /// + /// Assign index to primitive for index-based prim scene graph representation. + /// -1 = root + /// + using PrimIdxAssignFunctin = std::function; + void RegisterPrimIdxAssignFunction(PrimIdxAssignFunctin fun) { + _prim_idx_assign_fun = fun; + } + + /// + /// Stage Meta construction callback function + /// + using StageMetaProcessFunction = std::function; + + /// + /// Register Stage metadatum processing callback function. + /// Called when after parsing Stage metadatum. + /// + void RegisterStageMetaProcessFunction(StageMetaProcessFunction fun) { + _stage_meta_process_fun = fun; + } + + /// + /// Prim Meta construction callback function + /// + // using PrimMetaProcessFunction = std::function; + + /// + /// Prim construction callback function + /// TODO: Refactor arguments + /// + /// @param[in] full_path Absolute Prim Path(e.g. "/scope/gmesh0") + /// @param[in] spec Specifier(`def`, `over` or `class`) + /// @param[in] primTypeName typeName of this Prim(e.g. "Mesh", "SphereLight") + /// @param[in] primIdx primitive index + /// @param[in] parentPrimIdx parent Prim index. -1 for root + /// @param[in] properties Prim properties + /// @param[in] in_meta Input Prim metadataum + /// @param[in] in_variantSetList Input VariantSet contents. + /// @return true upon success or error message. + /// + using PrimConstructFunction = + std::function( + const Path &full_path, const Specifier spec, + const std::string &primTypeName, const Path &prim_name, + const int64_t primIdx, const int64_t parentPrimIdx, + const std::map &properties, + const PrimMetaMap &in_meta, const VariantSetList &in_variantSetList)>; + + /// + /// Register Prim construction callback function. + /// Example: "Xform", ReconstrctXform + /// + void RegisterPrimConstructFunction(const std::string &prim_type, + PrimConstructFunction fun) { + _prim_construct_fun_map[prim_type] = fun; + } + + /// + /// Callbacks called at closing `def` block. + /// + using PostPrimConstructFunction = + std::function( + const Path &path, const int64_t primIdx, + const int64_t parentPrimIdx)>; + void RegisterPostPrimConstructFunction(const std::string &prim_type, + PostPrimConstructFunction fun) { + _post_prim_construct_fun_map[prim_type] = fun; + } + + /// + /// For composition(Treat Prim as generic container). + /// AsciiParser(i.e. USDAReader) + /// + using PrimSpecFunction = std::function( + const Path &full_path, const Specifier spec, + const std::string &primTypeName, const Path &prim_name, + const int64_t primIdx, const int64_t parentPrimIdx, + const std::map &properties, + const PrimMetaMap &in_meta, const VariantSetList &in_variantSetLists)>; + + void RegisterPrimSpecFunction(PrimSpecFunction fun) { _primspec_fun = fun; } + + /// + /// Base filesystem directory to search asset files. + /// + void SetBaseDir(const std::string &base_dir); + + /// + /// Set ASCII data stream + /// + void SetStream(tinyusdz::StreamReader *sr); + + /// + /// Check if header data is USDA + /// + bool CheckHeader(); + + /// + /// True: create PrimSpec instead of typed Prim. + /// Set true if you do USD composition. + /// + void set_primspec_mode(bool onoff) { _primspec_mode = onoff; } + + /// + /// Parser entry point + /// + /// @param[in] load_states Bit mask of LoadState + /// @param[in] parser_option Parse option(optional) + /// + /// TODO: Move `load_states` to AsciiParserOption? + /// + bool Parse( + const uint32_t load_states = static_cast(LoadState::Toplevel), + const AsciiParserOption &parser_option = AsciiParserOption()); + + /// + /// Parse TimeSample value with specified array type of + /// `type_id`(value::TypeId) (You can obrain type_id from string using + /// value::GetTypeId()) + /// + bool ParseTimeSampleValue(const uint32_t type_id, value::Value *result); + + /// + /// Parse TimeSample value with specified `type_name`(Appears in USDA. .e.g. + /// "float", "matrix2d") + /// + bool ParseTimeSampleValue(const std::string &type_name, value::Value *result); + + /// + /// Parse TimeSample value with specified base type of + /// `type_id`(value::TypeId) (You can obrain type_id from string using + /// value::GetTypeId()) + /// + bool ParseTimeSampleValueOfArrayType(const uint32_t base_type_id, + value::Value *result); + + /// + /// Parse TimeSample value with specified array type of `type_name`("[]" + /// omiotted. .e.g. "float" for "float[]") + /// + bool ParseTimeSampleValueOfArrayType(const std::string &type_name, + value::Value *result); + + // TODO: ParseBasicType? + bool ParsePurpose(Purpose *result); + + /// + /// Return true but `value` is set to nullopt for `None`(Attribute Blocked) + /// + // template + // bool ReadBasicType(nonstd::optional *value); + + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + bool ReadBasicType(nonstd::optional *value); + + // template + // bool ReadBasicType(T *value); + + bool ReadBasicType(bool *value); + bool ReadBasicType(value::half *value); + bool ReadBasicType(value::half2 *value); + bool ReadBasicType(value::half3 *value); + bool ReadBasicType(value::half4 *value); + bool ReadBasicType(int32_t *value); + bool ReadBasicType(value::int2 *value); + bool ReadBasicType(value::int3 *value); + bool ReadBasicType(value::int4 *value); + bool ReadBasicType(uint32_t *value); + bool ReadBasicType(value::uint2 *value); + bool ReadBasicType(value::uint3 *value); + bool ReadBasicType(value::uint4 *value); + bool ReadBasicType(int64_t *value); + bool ReadBasicType(uint64_t *value); + bool ReadBasicType(float *value); + bool ReadBasicType(value::float2 *value); + bool ReadBasicType(value::float3 *value); + bool ReadBasicType(value::float4 *value); + bool ReadBasicType(double *value); + bool ReadBasicType(value::double2 *value); + bool ReadBasicType(value::double3 *value); + bool ReadBasicType(value::double4 *value); + bool ReadBasicType(value::quath *value); + bool ReadBasicType(value::quatf *value); + bool ReadBasicType(value::quatd *value); + bool ReadBasicType(value::point3h *value); + bool ReadBasicType(value::point3f *value); + bool ReadBasicType(value::point3d *value); + bool ReadBasicType(value::vector3h *value); + bool ReadBasicType(value::vector3f *value); + bool ReadBasicType(value::vector3d *value); + bool ReadBasicType(value::normal3h *value); + bool ReadBasicType(value::normal3f *value); + bool ReadBasicType(value::normal3d *value); + bool ReadBasicType(value::color3h *value); + bool ReadBasicType(value::color3f *value); + bool ReadBasicType(value::color3d *value); + bool ReadBasicType(value::color4h *value); + bool ReadBasicType(value::color4f *value); + bool ReadBasicType(value::color4d *value); + bool ReadBasicType(value::texcoord2h *value); + bool ReadBasicType(value::texcoord2f *value); + bool ReadBasicType(value::texcoord2d *value); + bool ReadBasicType(value::texcoord3h *value); + bool ReadBasicType(value::texcoord3f *value); + bool ReadBasicType(value::texcoord3d *value); + bool ReadBasicType(value::matrix2f *value); + bool ReadBasicType(value::matrix3f *value); + bool ReadBasicType(value::matrix4f *value); + bool ReadBasicType(value::matrix2d *value); + bool ReadBasicType(value::matrix3d *value); + bool ReadBasicType(value::matrix4d *value); + bool ReadBasicType(value::StringData *value); + bool ReadBasicType(std::string *value); + bool ReadBasicType(value::token *value); + bool ReadBasicType(Path *value); + bool ReadBasicType(value::AssetPath *value); + bool ReadBasicType(Reference *value); + bool ReadBasicType(Payload *value); + bool ReadBasicType(Identifier *value); + bool ReadBasicType(PathIdentifier *value); + + template + bool ReadBasicType(nonstd::optional> *value); + + template + bool ReadBasicType(std::vector *value); + + bool ParseMatrix(value::matrix2f *result); + bool ParseMatrix(value::matrix3f *result); + bool ParseMatrix(value::matrix4f *result); + + bool ParseMatrix(value::matrix2d *result); + bool ParseMatrix(value::matrix3d *result); + bool ParseMatrix(value::matrix4d *result); + + /// + /// Parse '(', Sep1By(','), ')' + /// + template + bool ParseBasicTypeTuple(std::array *result); + + /// + /// Parse '(', Sep1By(','), ')' + /// Can have `None` + /// + template + bool ParseBasicTypeTuple(nonstd::optional> *result); + + template + bool ParseTupleArray(std::vector> *result); + + /// + /// Parse the array of tuple. some may be None(e.g. `float3`: [(0, 1, 2), + /// None, (2, 3, 4), ...] ) + /// + template + bool ParseTupleArray(std::vector>> *result); + + template + bool SepBy1BasicType(const char sep, std::vector *result); + + /// + /// Allow the appearance of `sep` in the last item of array. + /// (e.g. `[1, 2, 3,]`) + /// + template + bool SepBy1BasicType(const char sep, const char end_symbol, + std::vector *result); + + /// + /// Parse '[', Sep1By(','), ']' + /// + template + bool ParseBasicTypeArray(std::vector> *result); + + /// + /// Parse '[', Sep1By(','), ']' + /// + template + bool ParseBasicTypeArray(std::vector *result); + + /// + /// Parses 1 or more occurences of value with basic type 'T', separated by + /// `sep` + /// + template + bool SepBy1BasicType(const char sep, + std::vector> *result); + + /// + /// Parses 1 or more occurences of tuple values with type 'T', separated by + /// `sep`. Allows 'None' + /// + template + bool SepBy1TupleType(const char sep, + std::vector>> *result); + + /// + /// Parses N occurences of tuple values with type 'T', separated by + /// `sep`. Allows 'None' + /// + template + bool SepByNTupleType( + const char sep, + std::array>, N> *result); + + /// + /// Parses 1 or more occurences of tuple values with type 'T', separated by + /// `sep` + /// + template + bool SepBy1TupleType(const char sep, std::vector> *result); + + bool ParseDictElement(std::string *out_key, MetaVariable *out_var); + bool ParseDict(std::map *out_dict); + + /// + /// Parse TimeSample data(scalar type) and store it to type-erased data + /// structure value::TimeSamples. + /// + /// @param[in] type_name Name of TimeSamples type(seen in .usda file. e.g. + /// "float" for `float var.timeSamples = ..`) + /// + bool ParseTimeSamples(const std::string &type_name, value::TimeSamples *ts); + + /// + /// Parse TimeSample data(array type) and store it to type-erased data + /// structure value::TimeSamples. + /// + /// @param[in] type_name Name of TimeSamples type(seen in .usda file. array + /// suffix `[]` is omitted. e.g. "float" for `float[] var.timeSamples = ..`) + /// + bool ParseTimeSamplesOfArray(const std::string &type_name, + value::TimeSamples *ts); + + /// + /// `variants` in Prim meta. + /// + bool ParseVariantsElement(std::string *out_key, std::string *out_var); + bool ParseVariants(VariantSelectionMap *out_map); + + bool MaybeListEditQual(tinyusdz::ListEditQual *qual); + bool MaybeVariability(tinyusdz::Variability *variability, + bool *varying_authored); + + /// + /// Try parsing single-quoted(`"`) string + /// + bool MaybeString(value::StringData *str); + + /// + /// Try parsing triple-quited(`"""`) multi-line string. + /// + bool MaybeTripleQuotedString(value::StringData *str); + + /// + /// Parse assset path identifier. + /// + bool ParseAssetIdentifier(value::AssetPath *out, bool *triple_deliminated); + +#if 0 + /// + /// + /// + std::string GetDefaultPrimName() const; + + /// + /// Get parsed toplevel "def" nodes(GPrim) + /// + std::vector GetGPrims(); +#endif + class PrimIterator; + using const_iterator = PrimIterator; + const_iterator begin() const; + const_iterator end() const; + + /// + /// Get error message(when `Parse` failed) + /// + std::string GetError(); + + /// + /// Get warning message(warnings in `Parse`) + /// + std::string GetWarning(); + +#if 0 + // Return the flag if the .usda is read from `references` + bool IsReferenced() { return _referenced; } + + // Return the flag if the .usda is read from `subLayers` + bool IsSubLayered() { return _sub_layered; } + + // Return the flag if the .usda is read from `payload` + bool IsPayloaded() { return _payloaded; } +#endif + + // Return true if the .udsa is read in the top layer(stage) + bool IsToplevel() { + return _toplevel; + // return !IsReferenced() && !IsSubLayered() && !IsPayloaded(); + } + + bool MaybeNone(); + bool MaybeCustom(); + + template + bool MaybeNonFinite(T *out); + + bool LexFloat(std::string *result); + + bool Expect(char expect_c); + + bool ReadStringLiteral( + std::string *literal); // identifier wrapped with " or '. result contains + // quote chars. + bool ReadPrimAttrIdentifier(std::string *token); + bool ReadIdentifier(std::string *token); // no '"' + bool ReadPathIdentifier( + std::string *path_identifier); // '<' + identifier + '>' + + // read until newline + bool ReadUntilNewline(std::string *str); + + + /// Parse magic + /// #usda FLOAT + bool ParseMagicHeader(); + + bool SkipWhitespace(); + + // skip_semicolon true: ';' can be used as a separator. this flag is for + // statement block. + bool SkipWhitespaceAndNewline(const bool allow_semicolon = true); + bool SkipCommentAndWhitespaceAndNewline(const bool allow_semicolon = true); + + bool SkipUntilNewline(); + + // bool ParseAttributeMeta(); + bool ParseAttrMeta(AttrMeta *out_meta); + + bool ParsePrimMetas(PrimMetaMap *out_metamap); + + bool ParseMetaValue(const VariableDef &def, MetaVariable *outvar); + + bool ParseStageMetaOpt(); + // Parsed Stage metadatum is stored in this instance. + bool ParseStageMetas(); + + bool ParseCustomMetaValue(); + + bool ParseReference(Reference *out, bool *triple_deliminated); + bool ParsePayload(Payload *out, bool *triple_deliminated); + + // `#` style comment + bool ParseSharpComment(); + + bool IsSupportedPrimAttrType(const std::string &ty); + bool IsSupportedPrimType(const std::string &ty); + bool IsSupportedAPISchema(const std::string &ty); + + bool Eof() { + // end of buffer, or current char is nullchar('\0') + return _sr->eof() || _sr->is_nullchar(); + } + + bool ParseRelationship(Relationship *result); + bool ParseProperties(std::map *props, + std::vector *propNames); + + // + // Look***() : Fetch chars but do not change input stream position. + // + + bool LookChar1(char *c); + bool LookCharN(size_t n, std::vector *nc); + + bool Char1(char *c); + bool CharN(size_t n, std::vector *nc); + + bool Rewind(size_t offset); + uint64_t CurrLoc(); + bool SeekTo(uint64_t pos); // Move to absolute `pos` bytes location + + bool PushParserState(); + bool PopParserState(ParseState *state); + + // + // Valid after ParseStageMetas() -------------- + // + StageMetas GetStageMetas() const { return _stage_metas; } + + // primIdx is assigned through `PrimIdxAssignFunctin` + // parentPrimIdx = -1 => root prim + // depth = tree level(recursion count) + // bool ParseClassBlock(const int64_t primIdx, const int64_t parentPrimIdx, + // const uint32_t depth = 0); bool ParseOverBlock(const int64_t primIdx, const + // int64_t parentPrimIdx, const uint32_t depth = 0); bool ParseDefBlock(const + // int64_t primIdx, const int64_t parentPrimIdx, const uint32_t depth = 0); + + // Parse `def`, `over` or `class` block + // @param[in] in_variantStmt : true when this Block is parsed within + // `variantSet` statement. Default true. + bool ParseBlock(const Specifier spec, const int64_t primIdx, + const int64_t parentPrimIdx, const uint32_t depth, + const bool in_variant = false); + + // Parse `varianntSet` stmt + bool ParseVariantSet(const int64_t primIdx, const int64_t parentPrimIdx, + const uint32_t depth, + std::map *variantSetMap); + + // -------------------------------------------- + + private: + /// + /// Do common setups. Assume called in ctor. + /// + void Setup(); + + nonstd::optional> ParsePrimMeta(); + bool ParsePrimProps(std::map *props, + std::vector *propNames); + + template + bool ParseBasicPrimAttr(bool array_qual, const std::string &primattr_name, + Attribute *out_attr); + + bool ParseStageMeta(std::pair *out); + + nonstd::optional GetStageMetaDefinition(const std::string &name); + nonstd::optional GetPrimMetaDefinition(const std::string &arg); + nonstd::optional GetPropMetaDefinition(const std::string &arg); + + std::string GetCurrentPrimPath(); + bool PrimPathStackDepth() { return _path_stack.size(); } + void PushPrimPath(const std::string &abs_path) { + // TODO: validate `abs_path` is really absolute full path. + _path_stack.push(abs_path); + } + void PopPrimPath() { + if (!_path_stack.empty()) { + _path_stack.pop(); + } + } + + const tinyusdz::StreamReader *_sr = nullptr; + + // "class" defs + // std::map _klasses; + std::stack _path_stack; + + Cursor _curr_cursor; + + // Supported Prim types + std::set _supported_prim_types; + std::set _supported_prim_attr_types; + + // Supported API schemas + std::set _supported_api_schemas; + + // Supported metadataum for Stage + std::map _supported_stage_metas; + + // Supported metadataum for Prim. + std::map _supported_prim_metas; + + // Supported metadataum for Property(Attribute and Relation). + std::map _supported_prop_metas; + + std::stack err_stack; + std::stack warn_stack; + std::stack parse_stack; + + float _version{1.0f}; + + // load flags + bool _toplevel{true}; + // TODO: deprecate? + bool _sub_layered{false}; + bool _referenced{false}; + bool _payloaded{false}; + + AsciiParserOption _option; + + std::string _base_dir; + + StageMetas _stage_metas; + + // + // Callbacks + // + PrimIdxAssignFunctin _prim_idx_assign_fun; + StageMetaProcessFunction _stage_meta_process_fun; + // PrimMetaProcessFunction _prim_meta_process_fun; + std::map _prim_construct_fun_map; + std::map _post_prim_construct_fun_map; + + bool _primspec_mode{false}; + + // For composition. PrimSpec is typeless so single callback function only. + PrimSpecFunction _primspec_fun{nullptr}; +}; + +/// +/// For USDC. +/// Parse string representation of UnregisteredValue(Attribute value). +/// e.g. "[(0, 1), (2, 3)]" for uint2[] type +/// +/// @param[in] typeName typeName(e.g. "uint2") +/// @param[in] str Ascii representation of value. +/// @param[out] value Ascii representation of value. +/// @param[out] err Parse error message when returning false. +/// +bool ParseUnregistredValue(const std::string &typeName, const std::string &str, + value::Value *value, std::string *err); + +} // namespace ascii + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/asset-resolution.cc b/contrib/tinyusdz/tinyusdz_repo/src/asset-resolution.cc new file mode 100644 index 000000000..6212b2ce0 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/asset-resolution.cc @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - Present, Light Transport Entertainment, Inc. +#include +#include + +#include "asset-resolution.hh" +#include "common-macros.inc" +#include "io-util.hh" +#include "value-pprint.hh" +#include "str-util.hh" + +namespace tinyusdz { + +std::string AssetResolutionResolver::search_paths_str() const { + std::string str; + + str += "[ "; + for (size_t i = 0; i < _search_paths.size(); i++) { + if (i > 0) { + str += ", "; + } + // TODO: Escape character? + str += _search_paths[i]; + } + str += " ]"; + return str; +} + +bool AssetResolutionResolver::find(const std::string &assetPath) const { + DCOUT("search_paths = " << _search_paths); + DCOUT("assetPath = " << assetPath); + + std::string ext = io::GetFileExtension(assetPath); + + if (_asset_resolution_handlers.count(ext)) { + if (_asset_resolution_handlers.at(ext).resolve_fun && _asset_resolution_handlers.at(ext).size_fun) { + std::string resolvedPath; + std::string err; + + // Use custom handler's userdata + void *userdata = _asset_resolution_handlers.at(ext).userdata; + + int ret = _asset_resolution_handlers.at(ext).resolve_fun(assetPath.c_str(), _search_paths, &resolvedPath, &err, userdata); + if (ret != 0) { + return false; + } + + uint64_t sz{0}; + ret = _asset_resolution_handlers.at(ext).size_fun(resolvedPath.c_str(), &sz, &err, userdata); + if (ret != 0) { + return false; + } + + return sz > 0; + + } else { + DCOUT("Either Resolve function or Size function is nullptr. Fallback to built-in file handler."); + } + } + + if ((_current_working_path == ".") || (_current_working_path == "./")) { + std::string rpath = io::FindFile(assetPath, {}); + } else { + // TODO: Only find when input path is relative. + std::string rpath = io::FindFile(assetPath, {_current_working_path}); + if (rpath.size()) { + return true; + } + } + + // TODO: Cache resolition. + std::string fpath = io::FindFile(assetPath, _search_paths); + return fpath.size(); + +} + +std::string AssetResolutionResolver::resolve( + const std::string &assetPath) const { + + std::string ext = io::GetFileExtension(assetPath); + + if (_asset_resolution_handlers.count(ext)) { + if (_asset_resolution_handlers.at(ext).resolve_fun) { + std::string resolvedPath; + std::string err; + + // Use custom handler's userdata + void *userdata = _asset_resolution_handlers.at(ext).userdata; + + int ret = _asset_resolution_handlers.at(ext).resolve_fun(assetPath.c_str(), _search_paths, &resolvedPath, &err, userdata); + if (ret != 0) { + return std::string(); + } + + return resolvedPath; + + } else { + DCOUT("Resolve function is nullptr. Fallback to built-in file handler."); + } + } + + DCOUT("cwd = " << _current_working_path); + DCOUT("search_paths = " << _search_paths); + DCOUT("assetPath = " << assetPath); + + std::string rpath; + if ((_current_working_path == ".") || (_current_working_path == "./")) { + rpath = io::FindFile(assetPath, {}); + } else { + rpath = io::FindFile(assetPath, {_current_working_path}); + } + + if (rpath.size()) { + return rpath; + } + + // TODO: Cache resolition. + return io::FindFile(assetPath, _search_paths); +} + +bool AssetResolutionResolver::open_asset(const std::string &resolvedPath, const std::string &assetPath, + Asset *asset_out, std::string *warn, std::string *err) const { + + if (!asset_out) { + if (err) { + (*err) = "`asset` arg is nullptr."; + } + return false; + } + + DCOUT("Opening asset: " << resolvedPath); + + (void)assetPath; + (void)warn; + + std::string ext = io::GetFileExtension(resolvedPath); + + if (_asset_resolution_handlers.count(ext)) { + if (_asset_resolution_handlers.at(ext).size_fun && _asset_resolution_handlers.at(ext).read_fun) { + + // Use custom handler's userdata + void *userdata = _asset_resolution_handlers.at(ext).userdata; + + // Get asset size. + uint64_t sz{0}; + int ret = _asset_resolution_handlers.at(ext).size_fun(resolvedPath.c_str(), &sz, err, userdata); + if (ret != 0) { + if (err) { + (*err) += "Get size of asset through handler failed.\n"; + } + return false; + } + + DCOUT("asset_size: " << sz); + + tinyusdz::Asset asset; + asset.resize(size_t(sz)); + + uint64_t read_size{0}; + + ret = _asset_resolution_handlers.at(ext).read_fun(resolvedPath.c_str(), /* req_size */asset.size(), asset.data(), &read_size, err, userdata); + + if (ret != 0) { + if (err) { + (*err) += "Read asset through handler failed.\n"; + } + return false; + } + + if (read_size < sz) { + asset.resize(size_t(read_size)); + // May optimize memory usage + asset.shrink_to_fit(); + } + + (*asset_out) = std::move(asset); + + return true; + } else { + DCOUT("Resolve function is nullptr. Fallback to built-in file handler."); + } + } + + // Default: read from a file. + std::vector data; + size_t max_bytes = 1024 * 1024 * _max_asset_bytes_in_mb; + if (!io::ReadWholeFile(&data, err, resolvedPath, max_bytes, + /* userdata */ nullptr)) { + + if (err) { + (*err) += "Open asset from a file failed.\n"; + } + + return false; + } + + asset_out->set_data(std::move(data)); + + return true; +} + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/asset-resolution.hh b/contrib/tinyusdz/tinyusdz_repo/src/asset-resolution.hh new file mode 100644 index 000000000..9da1ee599 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/asset-resolution.hh @@ -0,0 +1,364 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - Present, Light Transport Entertainment, Inc. +// +// Asset Resolution utilities +// https://graphics.pixar.com/usd/release/api/ar_page_front.html +// +// To avoid a confusion with AR(Argumented Reality), we doesn't use abberation +// `ar`, `Ar` and `AR`. ;-) +#pragma once + +#include +#include +#include + +#include "nonstd/optional.hpp" +#include "value-types.hh" + +namespace tinyusdz { + +/// +/// Abstract class for asset(e.g. file, memory, uri, ...) +/// Similar to ArAsset in pxrUSD. +/// +class Asset { + public: + size_t size() const { return buf_.size(); } + + const uint8_t *data() const { return buf_.data(); } + + uint8_t *data() { return buf_.data(); } + + void resize(size_t sz) { buf_.resize(sz); } + + void shrink_to_fit() { buf_.shrink_to_fit(); } + + void set_data(const std::vector &&rhs) { + buf_ = rhs; + } + + void set_name(const std::string &name) { + name_ = name; + } + + void set_resolved_name(const std::string &name) { + resolved_name_ = name; + } + + const std::string &name() const { + return name_; + } + + const std::string &resolved_name() const { + return resolved_name_; + } + + void set_version(const std::string &version) { + version_ = version; + } + + const std::string &version() const { + return version_; + } + + private: + std::string version_; // optional. + std::string name_; + std::string resolved_name_; + std::vector buf_; +}; + + +struct ResolverAssetInfo { + std::string version; + std::string assetName; + // std::string repoPath; deprecated in pxrUSD Ar 2.0 + + value::Value resolverInfo; +}; + +/// +/// For easier language bindings(e.g. for C), we use simple callback function +/// approach. +/// + +// Resolve asset. +// +// @param[in] asset_name Asset name or filepath +// @param[in] search_paths Search paths. +// @param[out] resolved_asset_name Resolved asset name. +// @param[out] err Error message. +// @param[inout] userdata Userdata. +// +// @return 0 upon success. -1 = asset cannot be resolved(not found). other negative value = error +typedef int (*FSResolveAsset)(const char *asset_name, const std::vector &search_paths, std::string *resolved_asset_name, + std::string *err, void *userdata); + +// @param[in] resolved_asset_name Resolved Asset name or filepath +// @param[out] nbytes Bytes of this asset. +// @param[out] err Error message. +// @param[inout] userdata Userdata. +// +// @return 0 upon success. negative value = error +typedef int (*FSSizeAsset)(const char *resolved_asset_name, uint64_t *nbytes, + std::string *err, void *userdata); + +// @param[in] resolved_asset_name Resolved Asset name or filepath +// @param[in] req_nbytes Required bytes for output buffer. +// @param[out] out_buf Output buffer. Memory should be allocated before calling this functione(`req_nbytes` or more) +// @param[out] nbytes Read bytes. 0 <= nbytes <= `req_nbytes` +// @param[out] err Error message. +// @param[inout] userdata Userdata. +// +// @return 0 upon success. negative value = error +typedef int (*FSReadAsset)(const char *resolved_asset_name, uint64_t req_nbytes, uint8_t *out_buf, + uint64_t *nbytes, std::string *err, void *userdata); + +// @param[in] asset_name Asset name or filepath(could be empty) +// @param[in] resolved_asset_name Resolved Asset name or filepath +// @param[in] buffer Data. +// @param[in] nbytes Data bytes. +// @param[out] err Error message. +// @param[inout] userdata Userdata. +// +// @return 0 upon success. negative value = error +typedef int (*FSWriteAsset)(const char *asset_name, const char *resolved_asset_name, const uint8_t *buffer, + const uint64_t nbytes, std::string *err, void *userdata); + +struct AssetResolutionHandler { + FSResolveAsset resolve_fun{nullptr}; + FSSizeAsset size_fun{nullptr}; + FSReadAsset read_fun{nullptr}; + FSWriteAsset write_fun{nullptr}; + void *userdata{nullptr}; +}; + +#if 0 // deprecated. +/// +/// @param[in] path Path string to be resolved. +/// @param[in] assetInfo nullptr when no `assetInfo` assigned to this path. +/// @param[inout] userdata Userdata pointer passed by callee. could be nullptr +/// @param[out] resolvedPath Resolved Path string. +/// @param[out] err Error message. +// +typedef bool (*ResolvePathHandler)(const std::string &path, + const ResolverAssetInfo *assetInfo, + void *userdata, std::string *resolvedPath, + std::string *err); +#endif + +class AssetResolutionResolver { + public: + AssetResolutionResolver() = default; + ~AssetResolutionResolver() {} + + AssetResolutionResolver(const AssetResolutionResolver &rhs) { + if (this != &rhs) { + //_resolve_path_handler = rhs._resolve_path_handler; + _asset_resolution_handlers = rhs._asset_resolution_handlers; + _userdata = rhs._userdata; + _search_paths = rhs._search_paths; + } + } + + AssetResolutionResolver &operator=(const AssetResolutionResolver &rhs) { + if (this != &rhs) { + // _resolve_path_handler = rhs._resolve_path_handler; + _asset_resolution_handlers = rhs._asset_resolution_handlers; + _userdata = rhs._userdata; + _search_paths = rhs._search_paths; + } + return (*this); + } + + AssetResolutionResolver &operator=(AssetResolutionResolver &&rhs) { + if (this != &rhs) { + //_resolve_path_handler = rhs._resolve_path_handler; + _asset_resolution_handlers = rhs._asset_resolution_handlers; + _userdata = rhs._userdata; + _search_paths = std::move(rhs._search_paths); + } + return (*this); + } + + // TinyUSDZ does not provide global search paths at the moment. + // static void SetDefaultSearchPath(const std::vector &p); + + void set_search_paths(const std::vector &paths) { + // TODO: Validate input paths. + _search_paths = paths; + } + + void add_search_path(const std::string &path) { + _search_paths.push_back(path); + } + + // + // Asset is first seeked from the current working path(directory) when the Asset's path is a relative path. + // + void set_current_working_path(const std::string &cwp) { + _current_working_path = cwp; + } + + const std::string ¤t_working_path() const { + return _current_working_path; + } + + const std::vector &search_paths() const { return _search_paths; } + + std::string search_paths_str() const; + + /// + /// Register user defined AssetResolution handler per file extension. + /// Default = use built-in file handler(FILE/ifstream) + /// This handler is used in resolve(), find() and open_asset() + /// + void register_asset_resolution_handler(const std::string &ext_name, AssetResolutionHandler handler) { + if (ext_name.empty()) { + return; + } + _asset_resolution_handlers[ext_name] = handler; + } + + bool unregister_asset_resolution_handler(const std::string &ext_name) { + if (_asset_resolution_handlers.count(ext_name)) { + _asset_resolution_handlers.erase(ext_name); + } + return false; + } + + bool has_asset_resolution_handler(const std::string &ext_name) { + return _asset_resolution_handlers.count(ext_name); + } + + +#if 0 + /// + /// Register user defined asset path resolver. + /// Default = find file from search paths. + /// + void register_resolve_path_handler(ResolvePathHandler handler) { + _resolve_path_handler = handler; + } + + void unregister_resolve_path_handler() { _resolve_path_handler = nullptr; } +#endif + + /// + /// Check if input asset exists(do asset resolution inside the function). + /// + /// @param[in] assetPath Asset path string(e.g. "bora.png", + /// "/mnt/c/sphere.usd") + /// + bool find(const std::string &assetPath) const; + + /// + /// Resolve asset path and returns resolved path as string. + /// Returns empty string when the asset does not exit. + /// + std::string resolve(const std::string &assetPath) const; + + /// + /// Open asset from the resolved Path. + /// + /// @param[in] resolvedPath Resolved path(through `resolve()`) + /// @param[in] assetPath Asset path(could be empty) + /// @param[out] asset Asset. + /// @param[out] warn Warning. + /// @param[out] err Error message. + /// + /// @return true upon success. + /// + bool open_asset(const std::string &resolvedPath, const std::string &assetPath, + Asset *asset, std::string *warn, std::string *err) const; + + void set_userdata(void *userdata) { _userdata = userdata; } + void *get_userdata() { return _userdata; } + const void *get_userdata() const { return _userdata; } + + void set_max_asset_bytes_in_mb(size_t megabytes) { + if (megabytes > 0) { + _max_asset_bytes_in_mb = megabytes; + } + } + + size_t get_max_asset_bytes_in_mb() const { + return _max_asset_bytes_in_mb; + } + + private: + //ResolvePathHandler _resolve_path_handler{nullptr}; + void *_userdata{nullptr}; + std::string _current_working_path{"./"}; + std::vector _search_paths; + mutable size_t _max_asset_bytes_in_mb{1024*1024}; // default 1 TB + + std::map _asset_resolution_handlers; + + // TODO: Cache resolution result + // mutable _dirty{true}; + // mutable std::map _cached_resolved_paths; +}; + +// forward decl +class PrimSpec; + +// +// Fileformat plugin(callback) interface. +// For fileformat which is used in `subLayers`, `reference` or `payload`. +// +// TinyUSDZ uses C++ callback interface for security. +// (On the contrary, pxrUSD uses `plugInfo.json` + dll). +// +// Texture image/Shader file(e.g. glsl) is not handled in this API. +// (Plese refer T.B.D. for texture/shader) +// +// TODO: Move to another header file? + +// Check if given data is a expectected file format +// +// @param[in] asset Asset data. +// @param[out] warn Warning message +// @param[out] err Error message(when the fuction returns false) +// @param[inout] user_data Userdata. can be nullptr. +// @return true when the given data is expected file format. +typedef bool (*FileFormatCheckFunction)(const Asset &asset, std::string *warn, std::string *err, void *user_data); + + +// Read content of data into PrimSpec(metadatum, properties, primChildren/variantChildren). +// +// TODO: Use `Layer` instead of `PrimSpec`? +// +// @param[in] asset Asset data +// @param[inout] ps PrimSpec which references/payload this asset. +// @param[out] warn Warning message +// @param[out] err Error message(when the fuction returns false) +// @param[inout] user_data Userdata. can be nullptr. +// +// @return true when reading data succeeds. +// +typedef bool (*FileFormatReadFunction)(const Asset &asset, PrimSpec &ps/* inout */, std::string *warn, std::string *err, void *user_data); + +// Write corresponding content of PrimSpec to a binary data +// +// @param[in] ps PrimSpec which refers this asset. +// @param[out] out_asset Output asset data. +// @param[out] warn Warning message +// @param[out] err Error message(when the fuction returns false) +// @param[inout] user_data Userdata. can be nullptr. +// @return true upon data write success. +typedef bool (*FileFormatWriteFunction)(const PrimSpec &ps, Asset *out_data, std::string *warn, std::string *err, void *user_data); + +struct FileFormatHandler +{ + std::string extension; // fileformat extension. + std::string description; // Description of this fileformat. can be empty. + + FileFormatCheckFunction checker{nullptr}; + FileFormatReadFunction reader{nullptr}; + FileFormatWriteFunction writer{nullptr}; + void *userdata{nullptr}; +}; + + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/common-macros.inc b/contrib/tinyusdz/tinyusdz_repo/src/common-macros.inc new file mode 100644 index 000000000..af86fc114 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/common-macros.inc @@ -0,0 +1,119 @@ +#pragma once + +#if !defined(TINYUSDZ_PRODUCTION_BUILD) && !defined(TINYUSDZ_FUZZER_BUILD) +#if defined(TINYUSDZ_DEBUG_PRINT) +#define TINYUSDZ_LOCAL_DEBUG_PRINT +#endif +#endif + +#if defined(TINYUSDZ_PRODUCTION_BUILD) +// Do not include full filepath for privacy. + +#define PUSH_ERROR_AND_RETURN(s) \ + do { \ + std::ostringstream ss_e; \ + ss_e << "[error]" \ + << ":" << __func__ << "():" << __LINE__ << " "; \ + ss_e << s << "\n"; \ + PushError(ss_e.str()); \ + return false; \ + } while (0) + +#define PUSH_ERROR_AND_RETURN_TAG(tag, s) \ + do { \ + std::ostringstream ss_e; \ + ss_e << "[error]" << tag << ":" << __func__ << "():" << __LINE__ << " "; \ + ss_e << s << "\n"; \ + PushError(ss_e.str()); \ + return false; \ + } while (0) + +#define PUSH_ERROR(s) \ + do { \ + std::ostringstream ss_e; \ + ss_e << "[error]" \ + << ":" << __func__ << "():" << __LINE__ << " "; \ + ss_e << s << "\n"; \ + PushError(ss_e.str()); \ + } while (0) + +#define PUSH_WARN(s) \ + do { \ + std::ostringstream ss_w; \ + ss_w << "[warn]" \ + << ":" << __func__ << "():" << __LINE__ << " "; \ + ss_w << s << "\n"; \ + PushWarn(ss_w.str()); \ + } while (0) + +#else // TINYUSDZ_PRODUCTION_BUILD + +#define PUSH_ERROR_AND_RETURN(s) \ + do { \ + std::ostringstream ss_e; \ + ss_e << "[error]" << __FILE__ << ":" << __func__ << "():" << __LINE__ \ + << " "; \ + ss_e << s << "\n"; \ + PushError(ss_e.str()); \ + return false; \ + } while (0) + +#define PUSH_ERROR_AND_RETURN_TAG(tag, s) \ + do { \ + std::ostringstream ss_e; \ + ss_e << "[error]" << __FILE__ << tag << ":" << __func__ \ + << "():" << __LINE__ << " "; \ + ss_e << s << "\n"; \ + PushError(ss_e.str()); \ + return false; \ + } while (0) + +#define PUSH_ERROR(s) \ + do { \ + std::ostringstream ss_e; \ + ss_e << "[error]" << __FILE__ << ":" << __func__ << "():" << __LINE__ \ + << " "; \ + ss_e << s << "\n"; \ + PushError(ss_e.str()); \ + } while (0) + +#define PUSH_WARN(s) \ + do { \ + std::ostringstream ss_w; \ + ss_w << "[warn]" << __FILE__ << ":" << __func__ << "():" << __LINE__ \ + << " "; \ + ss_w << s << "\n"; \ + PushWarn(ss_w.str()); \ + } while (0) + +#endif // TINYUSDZ_PRODUCTION_BUILD + +#if defined(TINYUSDZ_LOCAL_DEBUG_PRINT) +#define DCOUT(x) \ + do { \ + std::cout << __FILE__ << ":" << __func__ << ":" \ + << std::to_string(__LINE__) << " " << x << "\n"; \ + } while (false) +#else +#define DCOUT(x) +#endif + +// Simple auto-free class +// Use this class when saving stack size is required(e.g. recursive function call). +// T must have default constructor +template +class AutoFree { + private: + T *_v{nullptr}; + + public: + + AutoFree() : _v(new T()) { + } + + ~AutoFree() { + delete _v; + } + + T &value() { return *_v; } +}; diff --git a/contrib/tinyusdz/tinyusdz_repo/src/composition.cc b/contrib/tinyusdz/tinyusdz_repo/src/composition.cc new file mode 100644 index 000000000..17b8183d4 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/composition.cc @@ -0,0 +1,1734 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2023 - Present, Light Transport Entertainment, Inc. + +#include "composition.hh" + +#include +#include + +#if defined(__linux__) +#include +#endif + +#include "asset-resolution.hh" +#include "common-macros.inc" +#include "io-util.hh" +#include "pprinter.hh" +#include "prim-pprint.hh" +#include "prim-reconstruct.hh" +#include "prim-types.hh" +#include "str-util.hh" +#include "tiny-format.hh" +#include "tinyusdz.hh" +#include "usdGeom.hh" +#include "usdLux.hh" +#include "usdMtlx.hh" +#include "usdShade.hh" +#include "usda-reader.hh" + +#define PushError(s) \ + if (err) { \ + (*err) += s; \ + } + +#define PushWarn(s) \ + if (warn) { \ + (*warn) += s; \ + } + +namespace tinyusdz { + +namespace prim { + +// template specialization forward decls. +// implimentations will be located in prim-reconstruct.cc +#define RECONSTRUCT_PRIM_DECL(__ty) \ + template <> \ + bool ReconstructPrim<__ty>(const PrimSpec &, __ty *, std::string *, \ + std::string *, const PrimReconstructOptions &) + +RECONSTRUCT_PRIM_DECL(Xform); +RECONSTRUCT_PRIM_DECL(Model); +RECONSTRUCT_PRIM_DECL(Scope); +RECONSTRUCT_PRIM_DECL(GeomPoints); +RECONSTRUCT_PRIM_DECL(GeomMesh); +RECONSTRUCT_PRIM_DECL(GeomCapsule); +RECONSTRUCT_PRIM_DECL(GeomCube); +RECONSTRUCT_PRIM_DECL(GeomCone); +RECONSTRUCT_PRIM_DECL(GeomCylinder); +RECONSTRUCT_PRIM_DECL(GeomSphere); +RECONSTRUCT_PRIM_DECL(GeomBasisCurves); +RECONSTRUCT_PRIM_DECL(GeomCamera); +RECONSTRUCT_PRIM_DECL(GeomSubset); +RECONSTRUCT_PRIM_DECL(SphereLight); +RECONSTRUCT_PRIM_DECL(DomeLight); +RECONSTRUCT_PRIM_DECL(DiskLight); +RECONSTRUCT_PRIM_DECL(DistantLight); +RECONSTRUCT_PRIM_DECL(CylinderLight); +RECONSTRUCT_PRIM_DECL(SkelRoot); +RECONSTRUCT_PRIM_DECL(SkelAnimation); +RECONSTRUCT_PRIM_DECL(Skeleton); +RECONSTRUCT_PRIM_DECL(BlendShape); +RECONSTRUCT_PRIM_DECL(Material); +RECONSTRUCT_PRIM_DECL(Shader); + +#undef RECONSTRUCT_PRIM_DECL + +} // namespace prim + +namespace { + +bool IsVisited(const std::vector> layer_names_stack, + const std::string &name) { + for (size_t i = 0; i < layer_names_stack.size(); i++) { + if (layer_names_stack[i].count(name)) { + return true; + } + } + return false; +} + +std::string GetExtension(const std::string &name) { + return to_lower(io::GetFileExtension(name)); +} + +bool IsUSDFileFormat(const std::string &name) { + std::string ext = GetExtension(name); + + // no 'usdz' + return (ext.compare("usd") == 0) || (ext.compare("usda") == 0) || + (ext.compare("usdc") == 0); +} + +#if defined(TINYUSDZ_WITH_USDOBJ) +bool IsWavefrontObjFileFormat(const std::string &name) { + std::string ext = GetExtension(name); + + return ext.compare("obj") == 0; +} +#endif + +bool IsMtlxFileFormat(const std::string &name) { + std::string ext = GetExtension(name); + + return ext.compare("mtlx") == 0; +} + +bool IsBuiltinFileFormat(const std::string &name) { + if (IsUSDFileFormat(name)) { + return true; + } + + if (IsMtlxFileFormat(name)) { + return true; + } + +#if defined(TINYUSDZ_WITH_USDOBJ) + if (IsWavefrontObjFileFormat(name)) { + return true; + } +#endif + + return false; +} + +// Copy assetresolver state to all PrimSpec in the tree. +bool PropagateAssetResolverState(uint32_t depth, PrimSpec &ps, + const std::string &cwp, + const std::vector &search_paths) { + if (depth > (1024 * 1024 * 512)) { + return false; + } + + if (depth == 0) { + DCOUT("current_working_path: " << cwp); + DCOUT("search_paths: " << search_paths); + } + + ps.set_asset_resolution_state(cwp, search_paths); + + for (auto &child : ps.children()) { + if (!PropagateAssetResolverState(depth + 1, child, cwp, search_paths)) { + return false; + } + } + + return true; +} + +// TODO: support loading non-USD asset +bool LoadAsset(AssetResolutionResolver &resolver, + const std::string ¤t_working_path, + const std::vector &search_paths, + const std::map &fileformats, + const value::AssetPath &assetPath, const Path &primPath, + Layer *dst_layer, const PrimSpec **dst_primspec_root, + const bool error_when_no_prims_found, + const bool error_when_asset_not_found, + const bool error_when_unsupported_fileformat, std::string *warn, + std::string *err) { + if (!dst_layer) { + PUSH_ERROR_AND_RETURN( + "[Internal error]. `dst_layer` output arg is nullptr."); + } + + std::string asset_path = assetPath.GetAssetPath(); + std::string ext = GetExtension(asset_path); + + if (asset_path.empty()) { + PUSH_ERROR_AND_RETURN( + "TODO: No assetPath but Prim path(e.g. ) in references."); + } + + // TODO: Use std::stack to manage AssetResolutionResolver state? + if (current_working_path.size()) { + resolver.set_current_working_path(current_working_path); + } + + if (search_paths.size()) { + resolver.set_search_paths(search_paths); + } + + // resolve path + // TODO: Store resolved path to Reference? + std::string resolved_path = resolver.resolve(asset_path); + + DCOUT("Loading references: " << resolved_path + << ", asset_path: " << asset_path); + + if (resolved_path.empty()) { + if (error_when_asset_not_found) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to resolve asset path `{}`", asset_path)); + } else { + PUSH_WARN(fmt::format("Asset not found: `{}`", asset_path)); +#if 0 // for debugging. print cwd. +#if defined(__linux__) + char pathname[4096]; + memset(pathname, 0, 4096); + char *pathname_p = getcwd(pathname, 4096); + + if (pathname_p == nullptr) { + PUSH_ERROR_AND_RETURN( + "Getting current working directory failed."); + } + + PUSH_WARN(fmt::format(" cwd = {}", std::string(pathname_p))); +#endif +#endif + PUSH_WARN( + fmt::format(" current working path: `{}`", current_working_path)); + PUSH_WARN(fmt::format(" resolver.current_working_path: `{}`", + resolver.current_working_path())); + PUSH_WARN(fmt::format(" search_paths: `{}`", search_paths)); + PUSH_WARN(fmt::format(" resolver.search_paths: `{}`", + resolver.search_paths())); + (*dst_primspec_root) = nullptr; + return true; + } + } + + resolver.set_search_paths(search_paths); + + // Use resolved asset_path's basedir for current working path. + // Add resolved asset_path's basedir to search path. + std::string base_dir = io::GetBaseDir(resolved_path); + if (base_dir.size()) { + DCOUT(fmt::format("Add `{}' to asset search path.", base_dir)); + + resolver.set_current_working_path(base_dir); + + resolver.add_search_path(base_dir); + } + + Asset asset; + if (!resolver.open_asset(resolved_path, asset_path, &asset, warn, err)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to open asset `{}`.", resolved_path)); + } + + DCOUT("Opened resolved assst: " << resolved_path + << ", asset_path: " << asset_path); + + if (IsBuiltinFileFormat(asset_path)) { + if (IsUSDFileFormat(asset_path) || IsMtlxFileFormat(asset_path)) { + // ok + } else { + // TODO: obj + if (error_when_unsupported_fileformat) { + PUSH_ERROR_AND_RETURN(fmt::format( + "TODO: Unknown/unsupported asset file format: {}", asset_path)); + } else { + PUSH_WARN(fmt::format( + "TODO: Unknown/unsupported asset file format. Skipped: {}", + asset_path)); + return true; + } + } + } else { + if (fileformats.count(ext)) { + DCOUT("Fileformat handler found for: " + ext); + + } else { + DCOUT("Unknown/unsupported fileformat: " + ext); + if (error_when_unsupported_fileformat) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Unknown/unsupported asset file format: {}", asset_path)); + } else { + PUSH_WARN(fmt::format( + "Unknown/unsupported asset file format. Skipped: {}", asset_path)); + return true; + } + } + } + + Layer layer; + std::string _warn; + std::string _err; + + if (IsUSDFileFormat(asset_path)) { + if (!LoadLayerFromMemory(asset.data(), asset.size(), asset_path, &layer, + &_warn, &_err)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to open `{}` as Layer: {}", asset_path, _err)); + } + } else if (IsMtlxFileFormat(asset_path)) { + // primPath must be '' + if (primPath.prim_part() != "/MaterialX") { + PUSH_ERROR_AND_RETURN("Prim path must be , but got: " + + primPath.prim_part()); + } + + PrimSpec ps; + if (!LoadMaterialXFromAsset(asset, asset_path, ps, &_warn, &_err)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to open mtlx asset `{}`", asset_path)); + } + + ps.name() = "MaterialX"; + layer.primspecs()["MaterialX"] = ps; + + } else { + if (fileformats.count(ext)) { + PrimSpec ps; + const FileFormatHandler &handler = fileformats.at(ext); + + if (!handler.reader(asset, ps, &_warn, &_err, handler.userdata)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to read asset `{}` error: {}", + asset_path, _err)); + } + + if (ps.name().empty()) { + PUSH_ERROR_AND_RETURN(fmt::format( + "PrimSpec element_name is empty. asset `{}`", asset_path)); + } + + layer.primspecs()[ps.name()] = ps; + DCOUT("Read asset from custom fileformat handler: " << ext); + } else { + PUSH_ERROR_AND_RETURN(fmt::format( + "FileFormat handler not found for asset `{}`", asset_path)); + } + } + + DCOUT("layer = " << print_layer(layer, 0)); + + // TODO: Recursively resolve `references` + + if (_warn.size()) { + if (warn) { + (*warn) += _warn; + } + } + + if (layer.primspecs().empty()) { + if (error_when_no_prims_found) { + PUSH_ERROR_AND_RETURN(fmt::format("No prims in layer `{}`", asset_path)); + } + + if (dst_primspec_root) { + (*dst_primspec_root) = nullptr; + } + + (*dst_layer) = std::move(layer); + + return true; + } + + const PrimSpec *src_ps{nullptr}; + + if (dst_primspec_root) { + std::string default_prim; + if (primPath.is_valid()) { + default_prim = primPath.prim_part(); + DCOUT("primPath = " << default_prim); + } else { + // Use `defaultPrim` metadatum + if (layer.metas().defaultPrim.valid()) { + default_prim = "/" + layer.metas().defaultPrim.str(); + DCOUT("layer.meta.defaultPrim = " << default_prim); + } else { + // Use the first Prim in the layer. + default_prim = "/" + layer.primspecs().begin()->first; + DCOUT("layer.primspecs[0].name = " << default_prim); + } + } + + if (!layer.find_primspec_at(Path(default_prim, ""), &src_ps, err)) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Failed to find PrimSpec `{}` in layer `{}`(resolved path: `{}`)", + default_prim, asset_path, resolved_path)); + } + + if (!src_ps) { + PUSH_ERROR_AND_RETURN("Internal error: PrimSpec pointer is nullptr."); + } + + if (!PropagateAssetResolverState(0, *const_cast(src_ps), + resolver.current_working_path(), + resolver.search_paths())) { + PUSH_ERROR_AND_RETURN( + "Store AssetResolver state to each PrimSpec failed.\n"); + } + + (*dst_primspec_root) = src_ps; + } + + // FIXME: This may be redundant, since assetresulution state is stored in + // each PrimSpec. + // TODO: Remove layer-level assetresulution state store? + // + // save assetresolution state for nested composition. + layer.set_asset_resolution_state(resolver.current_working_path(), + resolver.search_paths(), + resolver.get_userdata()); + + (*dst_layer) = std::move(layer); + + return true; +} + +bool CompositeSublayersRec(AssetResolutionResolver &resolver, + const Layer &in_layer, + std::vector> layer_names_stack, + Layer *composited_layer, std::string *warn, + std::string *err, + const SublayersCompositionOptions &options) { + if (layer_names_stack.size() > options.max_depth) { + if (err) { + (*err) += "subLayer is nested too deeply."; + } + return false; + } + + layer_names_stack.emplace_back(std::set()); + std::set &curr_layer_names = layer_names_stack.back(); + + for (const auto &layer : in_layer.metas().subLayers) { + // TODO: subLayerOffset + std::string sublayer_asset_path = layer.assetPath.GetAssetPath(); + DCOUT("Load subLayer " << sublayer_asset_path); + + // Do cyclic referencing check. + // TODO: Use resolved name? + if (IsVisited(layer_names_stack, sublayer_asset_path)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Circular referenceing detected for subLayer: {} in {}", + sublayer_asset_path, in_layer.name())); + } + + std::string layer_filepath = resolver.resolve(sublayer_asset_path); + if (layer_filepath.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("{} not found in path: {}", + sublayer_asset_path, + resolver.search_paths_str())); + } + + tinyusdz::Layer sublayer; + if (!LoadAsset(resolver, in_layer.get_current_working_path(), + in_layer.get_asset_search_paths(), options.fileformats, + layer.assetPath, /* not_used */ Path::make_root_path(), + &sublayer, /* primspec_root */ nullptr, + options.error_when_no_prims_in_sublayer, + options.error_when_asset_not_found, + options.error_when_unsupported_fileformat, warn, err)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Load asset in subLayer failed: `{}`", layer.assetPath)); + } + + curr_layer_names.insert(sublayer_asset_path); + + Layer composited_sublayer; + + // Recursively load subLayer + if (!CompositeSublayersRec(resolver, sublayer, layer_names_stack, + &composited_sublayer, warn, err, options)) { + return false; + } + + { + // 1/2. merge sublayer's sublayers + + // NOTE: `over` specifier is ignored when merging Prims among different + // subLayers + for (auto &prim : composited_sublayer.primspecs()) { + if (composited_layer->has_primspec(prim.first)) { + // Skip + } else { + if (!composited_layer->emplace_primspec(prim.first, std::move(prim.second))) { + PUSH_ERROR_AND_RETURN( + fmt::format("Compositing PrimSpec {} in {} failed.", prim.first, + layer_filepath)); + } + DCOUT("add primspec: " << prim.first); + } + } + + // 2/2. merge sublayer + for (auto &prim : sublayer.primspecs()) { + if (composited_layer->has_primspec(prim.first)) { + // Skip + } else { + if (!composited_layer->emplace_primspec(prim.first, std::move(prim.second))) { + PUSH_ERROR_AND_RETURN( + fmt::format("Compositing PrimSpec {} in {} failed.", prim.first, + layer_filepath)); + } + DCOUT("add primspec: " << prim.first); + } + } + } + } + + layer_names_stack.pop_back(); + + return true; +} + +} // namespace + +bool CompositeSublayers(AssetResolutionResolver &resolver, + const Layer &in_layer, Layer *composited_layer, + std::string *warn, std::string *err, + SublayersCompositionOptions options) { + if (!composited_layer) { + return false; + } + + std::vector> layer_names_stack; + + DCOUT("Resolve subLayers.."); + if (!CompositeSublayersRec(resolver, in_layer, layer_names_stack, + composited_layer, warn, err, options)) { + PUSH_ERROR_AND_RETURN("Composite subLayers failed."); + } + + // merge Prims in root layer. + // NOTE: local Prims(prims in root layer) wins against subLayer's Prim + DCOUT("in_layer # of primspecs: " << in_layer.primspecs().size()); + for (auto &prim : in_layer.primspecs()) { + DCOUT("in_layer.prim: " << prim.first); + if (composited_layer->has_primspec(prim.first)) { + // over + if (prim.second.specifier() == Specifier::Class) { + // TODO: Simply ignore? + DCOUT("TODO: `class` Prim"); + } else if (prim.second.specifier() == Specifier::Over) { + PrimSpec &dst = composited_layer->primspecs()[prim.first]; + if (!OverridePrimSpec(dst, prim.second, warn, err)) { + return false; + } + } else if (prim.second.specifier() == Specifier::Def) { + DCOUT("overewrite prim: " << prim.first); + // overwrite + if (!composited_layer->replace_primspec(prim.first, prim.second)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to replace PrimSpec: {}", prim.first)); + } + } else { + /// ??? + PUSH_ERROR_AND_RETURN(fmt::format("Prim {} has invalid Prim specifier.", + prim.second.name())); + } + } else { + if (!composited_layer->add_primspec(prim.first, prim.second)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Compositing PrimSpec {} in {} failed.", prim.first, + in_layer.name())); + } + DCOUT("added primspec: " << prim.first); + } + } + + composited_layer->metas() = in_layer.metas(); + // Remove subLayers metadatum + composited_layer->metas().subLayers.clear(); + + DCOUT("Composite subLayers ok."); + return true; +} + +namespace { + +#if 0 +static bool FindPrimSpecRec(const std::string &parent_path, const Path &path, + const PrimSpec &parent, + const PrimSpec **foundPrimSpec, uint32_t depth) { + if (depth > 1024 * 1024 * 256) { + return false; + } + + std::string abs_path; + { + std::string elementName = parent.name(); + abs_path = parent_path + "/" + elementName; + DCOUT(fmt::format("findPrimSpec: {}, abs_path {}", path.full_path_name(), abs_path)); + if (abs_path == path.full_path_name()) { + (*foundPrimSpec) = &parent; + return true; + } + } + + for (const auto &child : parent.children()) { + if (FindPrimSpecRec(abs_path, path, child, foundPrimSpec, depth + 1)) { + return true; + } + } + + return false; +} + +// TODO: cache result. +static bool FindPrimSpecAt(const Path &path, const PrimSpec &rootPS, + const PrimSpec **foundPS, std::string *err) { + if (!path.is_valid()) { + if (err) { + (*err) += "Path is invalid.\n"; + } + return false; + } + + if (path.is_relative_path()) { + if (err) { + (*err) += "TODO: Relative path.\n"; + } + return false; + } + + if (!path.is_absolute_path()) { + if (err) { + (*err) += "Path is not absolute: " + path.full_path_name() + "\n"; + } + return false; + } + + bool ret = FindPrimSpecRec("", path, rootPS, foundPS, 0); + + if (!ret) { + if (err) { + (*err) += "Prim path " + path.full_path_name() + + " not found in given PrimSpec tree.\n"; + } + } + + return ret; +} +#endif + + +bool CompositeReferencesRec(uint32_t depth, AssetResolutionResolver &resolver, + const std::vector &asset_search_paths, + const Layer &in_layer, + PrimSpec &primspec /* [inout] */, std::string *warn, + std::string *err, + const ReferencesCompositionOptions &options) { + if (depth > options.max_depth) { + PUSH_ERROR_AND_RETURN("Too deep."); + } + + // Traverse children first. + for (auto &child : primspec.children()) { + if (!CompositeReferencesRec(depth + 1, resolver, asset_search_paths, in_layer, child, + warn, err, options)) { + return false; + } + } + + // Use PrimSpec's AssetResolution state. + std::string cwp = primspec.get_current_working_path(); + std::vector search_paths = primspec.get_asset_search_paths(); + + if (primspec.metas().references) { + const ListEditQual &qual = primspec.metas().references.value().first; + const auto &refecences = primspec.metas().references.value().second; + + if ((qual == ListEditQual::ResetToExplicit) || + (qual == ListEditQual::Prepend)) { + for (const auto &reference : refecences) { + Layer layer; + const PrimSpec *src_ps{nullptr}; + + if (reference.asset_path.GetAssetPath().empty()) { + if (reference.prim_path.is_absolute_path()) { + // Inherit-like operation. + + if (!in_layer.find_primspec_at(reference.prim_path, &src_ps, err)) { + return false; + } + + } else { + PUSH_ERROR_AND_RETURN( + fmt::format("Invalid asset path. assetPath is empty and " + "primPath is not absolute path: {}", + reference.prim_path.full_path_name())); + } + } else { + + DCOUT("reference.prim_path = " << reference.prim_path); + DCOUT("primspec.cwp = " << cwp); + DCOUT("primspec.search_paths = " << search_paths); + if (!LoadAsset(resolver, cwp, search_paths, options.fileformats, + reference.asset_path, reference.prim_path, &layer, + &src_ps, /* error_when_no_prims_found */ true, + options.error_when_asset_not_found, + options.error_when_unsupported_fileformat, warn, err)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to `references` asset `{}`", + reference.asset_path.GetAssetPath())); + } + } + + if (!src_ps) { + // LoadAsset allowed not-found or unsupported file. so do nothing. + continue; + } + + // `inherits` op + if (!InheritPrimSpec(primspec, *src_ps, warn, err)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to reference layer `{}`", + reference.asset_path)); + } + + // Modify Prim type if this PrimSpec is Model type. + if (primspec.typeName().empty() || primspec.typeName() == "Model") { + if (src_ps->typeName().empty() || src_ps->typeName() == "Model") { + // pass + } else { + primspec.typeName() = src_ps->typeName(); + } + } + + DCOUT("inherit done: primspec = " << primspec.name()); + } + + } else if (qual == ListEditQual::Delete) { + PUSH_ERROR_AND_RETURN("`delete` references are not supported yet."); + } else if (qual == ListEditQual::Add) { + PUSH_ERROR_AND_RETURN("`add` references are not supported yet."); + } else if (qual == ListEditQual::Order) { + PUSH_ERROR_AND_RETURN("`order` references are not supported yet."); + } else if (qual == ListEditQual::Invalid) { + PUSH_ERROR_AND_RETURN("Invalid listedit qualifier to for `references`."); + } else if (qual == ListEditQual::Append) { + for (const auto &reference : refecences) { + Layer layer; + const PrimSpec *src_ps{nullptr}; + + if (reference.asset_path.GetAssetPath().empty()) { + if (reference.prim_path.is_absolute_path()) { + // Inherit-like operation. + + if (!in_layer.find_primspec_at(reference.prim_path, &src_ps, err)) { + return false; + } + + } else { + PUSH_ERROR_AND_RETURN( + fmt::format("Invalid asset path. assetPath is empty and " + "primPath is not absolute path: {}", + reference.prim_path.full_path_name())); + } + } else { + if (!LoadAsset(resolver, cwp, search_paths, options.fileformats, + reference.asset_path, reference.prim_path, &layer, + &src_ps, /* error_when_no_prims */ true, + options.error_when_asset_not_found, + options.error_when_unsupported_fileformat, warn, err)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to `references` asset `{}`", + reference.asset_path.GetAssetPath())); + } + } + + if (!src_ps) { + // LoadAsset allowed not-found or unsupported file. so do nothing. + continue; + } + + // `over` op + if (!OverridePrimSpec(primspec, *src_ps, warn, err)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to reference layer `{}`", + reference.asset_path)); + } + + // Modify Prim type if this PrimSpec is Model type. + if (primspec.typeName().empty() || primspec.typeName() == "Model") { + if (src_ps->typeName().empty() || src_ps->typeName() == "Model") { + // pass + } else { + primspec.typeName() = src_ps->typeName(); + } + } + } + } + + } + + // Remove `references`. + primspec.metas().references.reset(); + + return true; +} + +bool CompositePayloadRec(uint32_t depth, AssetResolutionResolver &resolver, + const std::vector &asset_search_paths, + const Layer &in_layer, + PrimSpec &primspec /* [inout] */, std::string *warn, + std::string *err, + const PayloadCompositionOptions &options) { + if (depth > options.max_depth) { + PUSH_ERROR_AND_RETURN("Too deep."); + } + + // Traverse children first. + for (auto &child : primspec.children()) { + if (!CompositePayloadRec(depth + 1, resolver, asset_search_paths, in_layer, child, + warn, err, options)) { + return false; + } + } + + // Use PrimSpec's AssetResolution state. + std::string cwp = primspec.get_current_working_path(); + std::vector search_paths = primspec.get_asset_search_paths(); + + if (primspec.metas().payload) { + const ListEditQual &qual = primspec.metas().payload.value().first; + const auto &payloads = primspec.metas().payload.value().second; + + if ((qual == ListEditQual::ResetToExplicit) || + (qual == ListEditQual::Prepend)) { + for (const auto &pl : payloads) { + std::string asset_path = pl.asset_path.GetAssetPath(); + DCOUT("asset_path = " << asset_path); + + Layer layer; + const PrimSpec *src_ps{nullptr}; + + if (pl.asset_path.GetAssetPath().empty()) { + if (pl.prim_path.is_absolute_path()) { + // Inherit-like operation. + + if (!in_layer.find_primspec_at(pl.prim_path, &src_ps, err)) { + return false; + } + + } else { + PUSH_ERROR_AND_RETURN( + fmt::format("primPath is not absolute path: {}", + pl.prim_path.full_path_name())); + } + } else { + + if (!LoadAsset(resolver, cwp, search_paths, options.fileformats, + pl.asset_path, pl.prim_path, &layer, &src_ps, + /* error_when_no_prims_found */ true, + options.error_when_asset_not_found, + options.error_when_unsupported_fileformat, warn, err)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to `references` asset `{}`", + pl.asset_path.GetAssetPath())); + } + } + + if (!src_ps) { + // LoadAsset allowed not-found or unsupported file. so do nothing. + continue; + } + + // `inherits` op + if (!InheritPrimSpec(primspec, *src_ps, warn, err)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to reference layer `{}`", asset_path)); + } + + // Modify Prim type if this PrimSpec is Model type. + if (primspec.typeName().empty() || primspec.typeName() == "Model") { + if (src_ps->typeName().empty() || src_ps->typeName() == "Model") { + // pass + } else { + primspec.typeName() = src_ps->typeName(); + } + } + + DCOUT("inherit done: primspec = " << primspec.name()); + } + + } else if (qual == ListEditQual::Delete) { + PUSH_ERROR_AND_RETURN("`delete` references are not supported yet."); + } else if (qual == ListEditQual::Add) { + PUSH_ERROR_AND_RETURN("`add` references are not supported yet."); + } else if (qual == ListEditQual::Order) { + PUSH_ERROR_AND_RETURN("`order` references are not supported yet."); + } else if (qual == ListEditQual::Invalid) { + PUSH_ERROR_AND_RETURN("Invalid listedit qualifier to for `references`."); + } else if (qual == ListEditQual::Append) { + for (const auto &pl : payloads) { + std::string asset_path = pl.asset_path.GetAssetPath(); + + Layer layer; + const PrimSpec *src_ps{nullptr}; + + if (pl.asset_path.GetAssetPath().empty()) { + if (pl.prim_path.is_absolute_path()) { + // Inherit-like operation. + + if (!in_layer.find_primspec_at(pl.prim_path, &src_ps, err)) { + return false; + } + + } else { + PUSH_ERROR_AND_RETURN( + fmt::format("primPath is not absolute path: {}", + pl.prim_path.full_path_name())); + } + } else { + + if (!LoadAsset(resolver, cwp, search_paths, options.fileformats, + pl.asset_path, pl.prim_path, &layer, &src_ps, + /* error_when_no_prims_found */ true, + options.error_when_asset_not_found, + options.error_when_unsupported_fileformat, warn, err)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to `references` asset `{}`", + pl.asset_path.GetAssetPath())); + } + } + + if (!src_ps) { + // LoadAsset allowed not-found or unsupported file. so do nothing. + continue; + } + + // `over` op + if (!OverridePrimSpec(primspec, *src_ps, warn, err)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to reference layer `{}`", asset_path)); + } + + // Modify Prim type if this PrimSpec is Model type. + if (primspec.typeName().empty() || primspec.typeName() == "Model") { + if (src_ps->typeName().empty() || src_ps->typeName() == "Model") { + // pass + } else { + primspec.typeName() = src_ps->typeName(); + } + } + } + } + + } + + // Remove `payload`. + primspec.metas().payload.reset(); + + return true; +} + +bool CompositeVariantRec(uint32_t depth, PrimSpec &primspec /* [inout] */, + std::string *warn, std::string *err) { + if (depth > (1024 * 1024)) { + PUSH_ERROR_AND_RETURN("Too deep."); + } + + // Traverse children first. + for (auto &child : primspec.children()) { + if (!CompositeVariantRec(depth + 1, child, warn, err)) { + return false; + } + } + + PrimSpec dst; + std::map + variant_selection; // empty = use variant settings in PrimSpec. + + if (!VariantSelectPrimSpec(dst, primspec, variant_selection, warn, err)) { + return false; + } + + primspec = std::move(dst); + + return true; +} + +bool CompositeInheritsRec(uint32_t depth, const Layer &layer, + PrimSpec &primspec /* [inout] */, std::string *warn, + std::string *err) { + if (depth > (1024 * 1024)) { + PUSH_ERROR_AND_RETURN("Too deep."); + } + + // Traverse children first. + for (auto &child : primspec.children()) { + if (!CompositeInheritsRec(depth + 1, layer, child, warn, err)) { + return false; + } + } + + if (primspec.metas().inherits) { + const auto &qual = primspec.metas().inherits.value().first; + const auto &inherits = primspec.metas().inherits.value().second; + + if (inherits.size() == 0) { + // no-op, just remove `inherits` metadataum. + primspec.metas().inherits.reset(); + return true; + } + + if (inherits.size() != 1) { + if (err) { + (*err) += "Multiple inheritance is not supporetd.\n"; + } + return false; + } + + const Path &inheritPath = inherits[0]; + + const PrimSpec *inheritPrimSpec{nullptr}; + + if (!layer.find_primspec_at(inheritPath, &inheritPrimSpec, err)) { + if (err) { + (*err) += "Inheirt primspec failed since Path <" + + inheritPath.prim_part() + "> not found or is invalid.\n"; + } + + return false; + } + + // TODO: listEdit + DCOUT("TODO: listEdit in `inherits`"); + (void)qual; + + if (inheritPrimSpec) { + if (!InheritPrimSpec(primspec, *inheritPrimSpec, warn, err)) { + return false; + } + + // remove `inherits` metadataum. + primspec.metas().inherits.reset(); + + } else { + // ??? + if (err) { + (*err) += + "Inernal error. PrimSpec is nullptr in CompositeInehritsRec.\n"; + } + return false; + } + } + + return true; +} + +} // namespace + +bool CompositeReferences(AssetResolutionResolver &resolver, + const Layer &in_layer, Layer *composited_layer, + std::string *warn, std::string *err, + ReferencesCompositionOptions options) { + if (!composited_layer) { + return false; + } + + std::vector search_paths = in_layer.get_asset_search_paths(); + + Layer dst = in_layer; // deep copy + + for (auto &item : dst.primspecs()) { + if (!CompositeReferencesRec(/* depth */ 0, resolver, search_paths, in_layer, + item.second, warn, err, options)) { + PUSH_ERROR_AND_RETURN("Composite `references` failed."); + } + } + + (*composited_layer) = dst; + + DCOUT("Composite `references` ok."); + return true; +} + +bool CompositePayload(AssetResolutionResolver &resolver, const Layer &in_layer, + Layer *composited_layer, std::string *warn, + std::string *err, PayloadCompositionOptions options) { + if (!composited_layer) { + return false; + } + + Layer dst = in_layer; // deep copy + + for (auto &item : dst.primspecs()) { + if (!CompositePayloadRec(/* depth */ 0, resolver, + item.second.get_asset_search_paths(), in_layer, item.second, + warn, err, options)) { + PUSH_ERROR_AND_RETURN("Composite `payload` failed."); + } + } + + (*composited_layer) = dst; + + DCOUT("Composite `payload` ok."); + return true; +} + +bool CompositeVariant(const Layer &in_layer, Layer *composited_layer, + std::string *warn, std::string *err) { + if (!composited_layer) { + return false; + } + + Layer dst = in_layer; // deep copy + + for (auto &item : dst.primspecs()) { + if (!CompositeVariantRec(/* depth */ 0, item.second, warn, err)) { + PUSH_ERROR_AND_RETURN("Composite `variantSet` failed."); + } + } + + (*composited_layer) = dst; + + DCOUT("Composite `variantSet` ok."); + return true; +} + +bool CompositeInherits(const Layer &in_layer, Layer *composited_layer, + std::string *warn, std::string *err) { + if (!composited_layer) { + return false; + } + + Layer dst = in_layer; // deep copy + + for (auto &item : dst.primspecs()) { + if (!CompositeInheritsRec(/* depth */ 0, dst, item.second, warn, err)) { + PUSH_ERROR_AND_RETURN("Composite `inherits` failed."); + } + } + + (*composited_layer) = dst; + + DCOUT("Composite `inherits` ok."); + return true; +} + +namespace detail { + +static nonstd::optional ReconstructPrimFromPrimSpec( + const PrimSpec &primspec, std::string *warn, std::string *err) { + (void)warn; + + // TODO: + // - propertyNames() + // - primChildrenNames() + +#define RECONSTRUCT_PRIM(__primty) \ + if (primspec.typeName() == value::TypeTraits<__primty>::type_name()) { \ + __primty typed_prim; \ + if (!prim::ReconstructPrim(primspec, &typed_prim, warn, err)) { \ + PUSH_ERROR("Failed to reconstruct Prim from PrimSpec " \ + << primspec.typeName() \ + << " elementName: " << primspec.name()); \ + return nonstd::nullopt; \ + } \ + typed_prim.meta = primspec.metas(); \ + typed_prim.name = primspec.name(); \ + typed_prim.spec = primspec.specifier(); \ + /*typed_prim.propertyNames() = properties; */ \ + /*typed_prim.primChildrenNames() = primChildren;*/ \ + value::Value primdata = typed_prim; \ + Prim prim(primspec.name(), primdata); \ + prim.prim_type_name() = primspec.typeName(); \ + /* also add primChildren to Prim */ \ + /* prim.metas().primChildren = primChildren; */ \ + return std::move(prim); \ + } else + + if (primspec.typeName() == "Model") { + // Code is mostly identical to RECONSTRUCT_PRIM. + // Difference is store primTypeName to Model class itself. + Model typed_prim; + if (!prim::ReconstructPrim(primspec, &typed_prim, warn, err)) { + PUSH_ERROR("Failed to reconstruct Model"); + return nonstd::nullopt; + } + typed_prim.meta = primspec.metas(); + typed_prim.name = primspec.name(); + typed_prim.prim_type_name = primspec.typeName(); + typed_prim.spec = primspec.specifier(); + // typed_prim.propertyNames() = properties; + // typed_prim.primChildrenNames() = primChildren; + value::Value primdata = typed_prim; + Prim prim(primspec.name(), primdata); + prim.prim_type_name() = primspec.typeName(); + /* also add primChildren to Prim */ + // prim.metas().primChildren = primChildren; + return std::move(prim); + } else + + RECONSTRUCT_PRIM(Xform) + RECONSTRUCT_PRIM(Model) + RECONSTRUCT_PRIM(Scope) + RECONSTRUCT_PRIM(GeomMesh) + RECONSTRUCT_PRIM(GeomPoints) + RECONSTRUCT_PRIM(GeomCylinder) + RECONSTRUCT_PRIM(GeomCube) + RECONSTRUCT_PRIM(GeomCone) + RECONSTRUCT_PRIM(GeomSphere) + RECONSTRUCT_PRIM(GeomCapsule) + RECONSTRUCT_PRIM(GeomBasisCurves) + RECONSTRUCT_PRIM(GeomCamera) + // RECONSTRUCT_PRIM(GeomSubset) + RECONSTRUCT_PRIM(SphereLight) + RECONSTRUCT_PRIM(DomeLight) + RECONSTRUCT_PRIM(CylinderLight) + RECONSTRUCT_PRIM(DiskLight) + RECONSTRUCT_PRIM(DistantLight) + RECONSTRUCT_PRIM(SkelRoot) + RECONSTRUCT_PRIM(Skeleton) + RECONSTRUCT_PRIM(SkelAnimation) + RECONSTRUCT_PRIM(BlendShape) + RECONSTRUCT_PRIM(Shader) + RECONSTRUCT_PRIM(Material) { + PUSH_WARN("TODO or unsupported prim type: " << primspec.typeName()); + return nonstd::nullopt; + } + +#undef RECONSTRUCT_PRIM +} + +static bool OverridePrimSpecRec(uint32_t depth, PrimSpec &dst, + const PrimSpec &src, std::string *warn, + std::string *err) { + (void)warn; + + if (depth > (1024 * 1024 * 128)) { + PUSH_ERROR_AND_RETURN("PrimSpec tree too deep."); + } + + DCOUT("update_from"); + DCOUT(print_prim_metas(src.metas(), 1)); + // Override metadataum + dst.metas().update_from(src.metas()); + DCOUT("update_from done"); + + // Override properties + for (const auto &prop : src.props()) { + // replace + dst.props()[prop.first] = prop.second; + } + + // Override child primspecs. + for (auto &child : dst.children()) { + auto src_it = std::find_if( + src.children().begin(), src.children().end(), + [&child](const PrimSpec &ps) { return ps.name() == child.name(); }); + + if (src_it != src.children().end()) { + if (!OverridePrimSpecRec(depth + 1, child, (*src_it), warn, err)) { + return false; + } + } + } + + // Add child not exists in dst. + for (auto &child : src.children()) { + auto dst_it = std::find_if( + dst.children().begin(), dst.children().end(), + [&child](const PrimSpec &ps) { return ps.name() == child.name(); }); + + if (dst_it == dst.children().end()) { + dst.children().push_back(child); + } + } + + return true; +} + +// +// TODO: Support nested inherits? +// +static bool InheritPrimSpecImpl(PrimSpec &dst, const PrimSpec &src, + std::string *warn, std::string *err) { + DCOUT("inherit begin\n"); + (void)warn; + + DCOUT("src = " << prim::print_primspec(src)); + + // Create PrimSpec from `src`, + // Then override it with `dst` + PrimSpec ps = src; // copy + + // Keep PrimSpec name, typeName and spec from `dst` + ps.name() = dst.name(); + ps.typeName() = dst.typeName(); + ps.specifier() = dst.specifier(); + + // Override metadataum + ps.metas().update_from(dst.metas()); + + // Override properties + for (const auto &prop : dst.props()) { + if (ps.props().count(prop.first)) { + // replace + ps.props().at(prop.first) = prop.second; + } + } + + // Overide child primspecs. + for (auto &child : ps.children()) { + auto src_it = std::find_if(dst.children().begin(), dst.children().end(), + [&child](const PrimSpec &primspec) { + return primspec.name() == child.name(); + }); + + if (src_it != dst.children().end()) { + if (!OverridePrimSpecRec(1, child, (*src_it), warn, err)) { + return false; + } + } + } + + DCOUT("move"); + dst = std::move(ps); + DCOUT("move done"); + + return true; +} + +} // namespace detail + +bool LayerToStage(const Layer &layer, Stage *stage_out, std::string *warn, + std::string *err) { + if (!stage_out) { + if (err) { + (*err) += "`stage_ptr` is nullptr."; + } + return false; + } + + Stage stage; + + stage.metas() = layer.metas(); + + // TODO: primChildren metadatum + for (const auto &primspec : layer.primspecs()) { + if (auto pv = + detail::ReconstructPrimFromPrimSpec(primspec.second, warn, err)) { + // TODO + (void)pv; + } + } + + (*stage_out) = stage; + + return true; +} + +bool OverridePrimSpec(PrimSpec &dst, const PrimSpec &src, std::string *warn, + std::string *err) { + if (src.specifier() != Specifier::Over) { + PUSH_ERROR("src PrimSpec must be qualified with `over` specifier.\n"); + } + + return detail::OverridePrimSpecRec(0, dst, src, warn, err); +} + +bool InheritPrimSpec(PrimSpec &dst, const PrimSpec &src, std::string *warn, + std::string *err) { + return detail::InheritPrimSpecImpl(dst, src, warn, err); +} + +#if 0 +bool ReferenceLayerToPrimSpec(PrimSpec &dst, const Layer &layer, + const Path primPath, + const LayerOffset layerOffset) { + if (layer.primspecs().empty()) { + // nothing to do + return true; + } + + std::string src_root_prim_name = ""; + if (!primPath.is_valid()) { + // Use the defaultPrim + if (!layer.metas().defaultPrim.str().empty()) { + src_root_prim_name = layer.metas().defaultPrim.str(); + } else { + // Use the first Prim. + src_root_prim_name = (layer.primspecs().begin())->first; + } + } else { + src_root_prim_name = primPath.prim_part(); + } + + DCOUT("TODO"); + (void)dst; + (void)layerOffset; + + return false; +} +#endif + +#if 0 +bool HasReferences(const Layer &layer, const bool force_check, + const ReferencesCompositionOptions options) { + if (!force_check) { + return layer.has_unresolved_references(); + } + + return layer.check_unresolved_references(options.max_depth); +} + +bool HasPayload(const Layer &layer, const bool force_check, + const PayloadCompositionOptions options) { + if (!force_check) { + return layer.has_unresolved_payload(); + } + + return layer.check_unresolved_payload(options.max_depth); +} + +bool HasInherits(const Layer &layer) { + return layer.check_unresolved_inherits(); +} + +bool HasOver(const Layer &layer) { return layer.check_over_primspec(); } + +bool HasSpecializes(const Layer &layer) { + return layer.check_unresolved_specializes(); +} +#endif + +namespace { + +bool ExtractVariantsRec(uint32_t depth, const std::string &root_path, + const PrimSpec &ps, Dictionary &dict, + const uint32_t max_depth, std::string *err) { + if (depth > max_depth) { + if (err) { + (*err) += "Too deep\n"; + } + return false; + } + + Dictionary variantInfos; + + if (ps.name().empty()) { + if (err) { + (*err) += "PrimSpec name is empty.\n"; + } + return false; + } + + std::string full_prim_path = root_path + "/" + ps.name(); + + if (ps.metas().variantSets) { + const std::vector &vsets = + ps.metas().variantSets.value().second; + MetaVariable var; + var.set_value(vsets); + variantInfos["variantSets"] = var; + } + + if (ps.metas().variants) { + Dictionary values; + + const VariantSelectionMap &vsmap = ps.metas().variants.value(); + for (const auto &item : vsmap) { + MetaVariable var; + var.set_value(item.second); + + values[item.first] = item.second; + } + + variantInfos["variants"] = values; + } + + if (variantInfos.size()) { + dict[full_prim_path] = variantInfos; + } + + // Traverse children + for (const auto &child : ps.children()) { + if (!ExtractVariantsRec(depth + 1, full_prim_path, child, dict, max_depth, + err)) { + return false; + } + } + + return true; +} + +bool ExtractVariantsRec(uint32_t depth, const std::string &root_path, + const Prim &prim, Dictionary &dict, + const uint32_t max_depth, std::string *err) { + if (depth > max_depth) { + if (err) { + (*err) += "Too deep\n"; + } + return false; + } + + Dictionary variantInfos; + + if (prim.element_name().empty()) { + if (err) { + (*err) += "Prim name is empty.\n"; + } + return false; + } + + std::string full_prim_path = root_path + "/" + prim.element_name(); + + if (prim.metas().variantSets) { + const std::vector &vsets = + prim.metas().variantSets.value().second; + MetaVariable var; + var.set_value(vsets); + variantInfos["variantSets"] = var; + } + + if (prim.metas().variants) { + Dictionary values; + + const VariantSelectionMap &vsmap = prim.metas().variants.value(); + for (const auto &item : vsmap) { + MetaVariable var; + var.set_value(item.second); + + values[item.first] = item.second; + } + + variantInfos["variants"] = values; + } + + // variantSetChildren Prim metadataum supercedes Prim's variantSets Stmt + if (prim.metas().variantSetChildren) { + const std::vector &vsets = + prim.metas().variantSetChildren.value(); + // to string + std::vector vsetchildren; + for (const auto &item : vsets) { + if (!item.valid()) { + if (err) { + (*err) += "Invalid variantSetChildren token found.\n"; + } + return false; + } + vsetchildren.push_back(item.str()); + } + variantInfos["variantSet"] = vsetchildren; + } else if (prim.variantSets().size()) { + Dictionary vsetdict; + + for (const auto &item : prim.variantSets()) { + if (item.second.variantSet.size()) { + std::vector variantStmtNames; + + if (item.second.name.empty()) { + if (err) { + (*err) += "Invalid variantSets Statements found.\n"; + } + return false; + } + + for (const auto &v : item.second.variantSet) { + variantStmtNames.push_back(v.first); + } + + vsetdict[item.first] = variantStmtNames; + } + } + + if (vsetdict.size()) { + variantInfos["variantSet"] = vsetdict; + } + } + + if (variantInfos.size()) { + dict[full_prim_path] = variantInfos; + } + + // Traverse children + for (const auto &child : prim.children()) { + if (!ExtractVariantsRec(depth + 1, full_prim_path, child, dict, max_depth, + err)) { + return false; + } + } + + return true; +} + +} // namespace + +bool ExtractVariants(const Layer &layer, Dictionary *dict, std::string *err) { + if (!dict) { + if (err) { + (*err) += "`dict` argument is nullptr.\n"; + } + + return false; + } + + for (const auto &primspec : layer.primspecs()) { + if (!ExtractVariantsRec(/* depth */ 0, /* root path */ "", primspec.second, + (*dict), /* max_depth */ 1024 * 1024, err)) { + return false; + } + } + + return true; +} + +bool ExtractVariants(const Stage &stage, Dictionary *dict, std::string *err) { + if (!dict) { + if (err) { + (*err) += "`dict` argument is nullptr.\n"; + } + + return false; + } + + for (const auto &prim : stage.root_prims()) { + if (!ExtractVariantsRec(/* depth */ 0, /* root path */ "", prim, (*dict), + /* max_depth */ 1024 * 1024, err)) { + return false; + } + } + + return true; +} + +bool VariantSelectPrimSpec( + PrimSpec &dst, const PrimSpec &src, + const std::map &variant_selection, + std::string *warn, std::string *err) { + if (src.metas().variants && src.metas().variantSets) { + // do variant compsotion + } else if (src.metas().variants) { + if (warn) { + (*warn) += + "`variants` are authored, but `variantSets` is not authored.\n"; + } + dst = src; + dst.metas().variants.reset(); + dst.metas().variantSets.reset(); + dst.variantSets().clear(); + return true; + } else if (src.metas().variantSets) { + if (warn) { + (*warn) += + "`variantSets` are authored, but `variants` is not authored.\n"; + } + dst = src; + dst.metas().variants.reset(); + dst.metas().variantSets.reset(); + dst.variantSets().clear(); + // nothing to do. + return true; + } else { + dst = src; + return true; + } + + const auto &variantSetMeta = src.metas().variantSets.value(); + + const ListEditQual qual = variantSetMeta.first; + (void)qual; + + dst = src; + + PrimSpec ps = src; // temp PrimSpec. Init with src. + + // Evaluate from the last element. + for (int64_t i = int64_t(variantSetMeta.second.size()) - 1; i >= 0; i--) { + const auto &variantSetName = variantSetMeta.second[size_t(i)]; + + // 1. look into `variant_selection`. + // 2. look into variant setting in this PrimSpec. + + std::string variantName; + if (variant_selection.count(variantSetName)) { + variantName = variant_selection.at(variantSetName); + } else if (dst.current_variant_selection(variantSetName, &variantName)) { + // ok + } else { + continue; + } + + if (dst.variantSets().count(variantSetName)) { + const auto &vss = dst.variantSets().at(variantSetName); + + if (vss.variantSet.count(variantName)) { + const PrimSpec &vs = vss.variantSet.at(variantName); + + DCOUT(fmt::format("variantSet[{}] Select variant: {}", variantSetName, + variantName)); + + // + // Promote variant content to PrimSpec. + // + + // over-like operation + ps.metas().update_from(vs.metas(), /* override_authored */ true); + + for (const auto &prop : vs.props()) { + DCOUT("prop: " << prop.first); + // override existing prop + ps.props()[prop.first] = prop.second; + } + + for (const auto &child : vs.children()) { + // Override if PrimSpec has same name + // simple linear scan. + auto it = std::find_if(ps.children().begin(), ps.children().end(), + [&child](const PrimSpec &item) { + return (item.name() == child.name()); + }); + + if (it != ps.children().end()) { + (*it) = child; // replace + } else { + ps.children().push_back(child); + } + } + + // TODO: + // - [ ] update `primChildren` and `properties` metadataum if required. + } + } + } + + DCOUT("Variant resolved prim: " << prim::print_primspec(ps)); + + // Local properties/metadatum wins against properties/metadataum from Variant + ps.specifier() = Specifier::Over; + if (!OverridePrimSpec(dst, ps, warn, err)) { + PUSH_ERROR_AND_RETURN("Failed to override PrimSpec."); + } + + dst.metas().variants.reset(); + dst.metas().variantSets.reset(); + dst.variantSets().clear(); + + return true; +} + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/composition.hh b/contrib/tinyusdz/tinyusdz_repo/src/composition.hh new file mode 100644 index 000000000..54ff9917e --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/composition.hh @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022-Present Light Transport Entertainment Inc. +// +// Layer and Prim composition features. +// +#pragma once + +#include "asset-resolution.hh" +#include "prim-types.hh" + +// TODO +// - [x] Compose `references` +// - [x] Compose `payloads` +// - [ ] Compose `specializes` +// - [x] Compose `inherits` +// - [ ] Compose `variantSets` +// - [x] Compose `over` +// - [ ] Consider `active` Prim metadatum + +namespace tinyusdz { + +// Forward decl. +class Stage; + +// USD asset loading state. +enum class LoadState : uint32_t { + Toplevel = 1, // load initial .usd(default) + Sublayer = 1 << 1, // load USD from Stage meta sublayer + Reference = 1 << 2, // load USD from Prim meta reference + Payload = 1 << 3 // load USD from Prim meta payload +}; + +struct SublayersCompositionOptions { + // The maximum depth for nested `subLayers` + uint32_t max_depth = 1024u; + + // Make an error when referenced asset does not contain prims. + bool error_when_no_prims_in_sublayer{false}; + + // Make an error when referenced asset is not found + bool error_when_asset_not_found{false}; + + // Make an error when referenced asset is unsupported(e.g. unknown file extension) + bool error_when_unsupported_fileformat{false}; + + // File formats + std::map fileformats; +}; + +struct ReferencesCompositionOptions { + // The maximum depth for nested `references` + uint32_t max_depth = 1024u; + + // Make an error when referenced asset is not found + bool error_when_asset_not_found{false}; + + // Make an error when referenced asset is unsupported(e.g. unknown file extension) + bool error_when_unsupported_fileformat{false}; + + // File formats + std::map fileformats; +}; + +struct PayloadCompositionOptions { + // The maximum depth for nested `payload` + uint32_t max_depth = 1024u; + + // Make an error when referenced asset is not found + bool error_when_asset_not_found{false}; + + // Make an error when referenced asset is unsupported(e.g. unknown file extension) + bool error_when_unsupported_fileformat{false}; + + // File formats + std::map fileformats; +}; + +/// +/// Return true when any PrimSpec in the Layer contains `references` Prim metadataum +/// +/// Layer has cached flag for quicky detecting whether Layer has unresolved `references` or not. +/// +/// @param[in] layer Layer +/// @param[in] force_check When true, traverse PrimSpec hierarchy and find `references` metadatum. Use cached flag in `layer` when false. +/// +bool HasReferences(const Layer &layer, const bool force_check = false, const ReferencesCompositionOptions options = ReferencesCompositionOptions()); + +/// +/// Return true when any PrimSpec in the Layer contains `payload` Prim metadataum +/// +/// Layer has cached flag for quicky detecting whether Layer has unresolved `payload` or not. +/// +/// @param[in] layer Layer +/// @param[in] force_check When true, traverse PrimSpec hierarchy and find `payload` metadatum. Use cached flag in `layer` when false. +/// +bool HasPayload(const Layer &layer, const bool force_check = false, const PayloadCompositionOptions options = PayloadCompositionOptions()); + +/// +/// Return true when any PrimSpec in the Layer contains `specializes` Prim metadataum. +/// We think specializers are not intensively used, so no caching. +/// +/// @param[in] layer Layer +/// +bool HasSpecializes(const Layer &layer); + +/// +/// Return true when any PrimSpec in the Layer contains `inherits` Prim metadataum. +/// +/// @param[in] layer Layer +/// +bool HasInherits(const Layer &layer); + +/// +/// Return true when any PrimSpec in the Layer contains `over` Prim. +/// +/// @param[in] layer Layer +/// +bool HasOver(const Layer &layer); + +#if 0 // deprecate it. +/// +/// Load subLayer USD files in `layer`, and return composited(flattened) Layer +/// to `composited_layer` Supply search_path with `base_dir` +/// +bool CompositeSublayers( + const std::string &base_dir, const Layer &layer, Layer *composited_layer, + std::string *warn, std::string *err, + const SublayersCompositionOptions options = SublayersCompositionOptions()); +#endif + +/// +/// Load subLayer USD files in `layer`, and return composited(flattened) Layer +/// to `composited_layer` Supply AssetResolutionResolver +/// +bool CompositeSublayers( + AssetResolutionResolver &resolver /* inout */, const Layer &layer, + Layer *composited_layer, std::string *warn, std::string *err, + const SublayersCompositionOptions options = SublayersCompositionOptions()); + +/// +/// Resolve `references` for each PrimSpe, and return composited(flattened) +/// Layer to `composited_layer` in `layer`. +/// +bool CompositeReferences(AssetResolutionResolver &resolver /* inout */, + const Layer &layer, Layer *composited_layer, + std::string *warn, std::string *err, + const ReferencesCompositionOptions options = + ReferencesCompositionOptions()); + +/// +/// Resolve `payload` for each PrimSpec, and return composited(flattened) Layer +/// to `composited_layer` in `layer`. +/// +bool CompositePayload( + AssetResolutionResolver &resolver /* inout */, const Layer &layer, + Layer *composited_layer, std::string *warn, std::string *err, + const PayloadCompositionOptions options = PayloadCompositionOptions()); + +/// +/// Resolve `variantSet` for each PrimSpec, and return composited(flattened) Layer +/// to `composited_layer` in `layer`. +/// Use variant selection info in each PrimSpec. +/// To externally specify variants to select, Use `ApplyVariantSelector`. +/// +bool CompositeVariant( + const Layer &layer, + Layer *composited_layer, std::string *warn, std::string *err); + +/// +/// Resolve `specializes` for each PrimSpec, and return composited(flattened) Layer +/// to `composited_layer` in `layer`. +/// +bool CompositeSpecializes(const Layer &layer, + Layer *composited_layer, std::string *warn, std::string *err); + +/// +/// Resolve `inherits` for each PrimSpec, and return composited(flattened) Layer +/// to `composited_layer` in `layer`. +/// +bool CompositeInherits(const Layer &layer, + Layer *composited_layer, std::string *warn, std::string *err); + +/// +/// Override a PrimSpec with another PrimSpec. +/// +/// @param[inout] dst PrimSpec to be override(must be `def` or `class` spec) +/// @param[in] src PrimSpec for override(must be `over` spec) +/// +/// @return true upon success. false when error. +/// +bool OverridePrimSpec(PrimSpec &dst, const PrimSpec &src, std::string *warn, + std::string *err); + +/// +/// Inherit PrimSpec. All PrimSpec tree in `src` PrimSpec will be inheritated to +/// `dst` PrimSpec. +/// +/// @param[inout] dst PrimSpec to be inheritated +/// @param[in] src Source PrimSpec. Source PrimSpec can be any specifier(i.e, +/// `class`, `def` or `over`), but `class` recommended. +/// +/// @return true upon success. false when error. +/// +bool InheritPrimSpec(PrimSpec &dst, const PrimSpec &src, std::string *warn, + std::string *err); + +/// +/// Build USD Stage from Layer +/// +bool LayerToStage(const Layer &layer, Stage *stage, std::string *warn, + std::string *err); + +/// +/// Build USD Stage from Layer +/// +/// `layer` object will be destroyed after `stage` is being build. +/// +bool LayerToStage(Layer &&layer, Stage *stage, std::string *warn, + std::string *err); + +struct VariantSelector { + std::string selection; // current selection + VariantSelectionMap vsmap; +}; + +using VariantSelectorMap = std::map; + +/// +/// Recursively traverse PrimSpec tree and collect variantSelection information. +/// +/// key : PrimSpec path(e.g. "/root/xform0") +/// value : VariantSelectionInfo +/// +/// TODO: Move to Tydra API? +/// +bool ListVariantSelectionMaps(const Layer &layer, VariantSelectorMap &m); + +/// +/// Select variant(PrimSpec subtree) `variant_name` from `src` PrimSpec and +/// write it to `dst` PrimSpec. +/// +/// @param[inout] dst PrimSpec where selected variant are written. +/// @param[in] src Source PrimSpec. Source PrimSpec. +/// @param[in] variant_selection Variant Selection list. key = variantSet name, value = variant name. Can be empty(when empty, use PrimSpec's variants information) +/// +/// @return true upon success. false when error. No error when any of variant info in `variant_selection` does not exist in `src` PrimSpec. +/// +bool VariantSelectPrimSpec(PrimSpec &dst, const PrimSpec &src, + const std::map &variant_selection, std::string *warn, + std::string *err); + +/// +/// Resolve variant in PrimSpec tree and write result to `dst`. +/// `dst` does not contain any variant info. +/// +bool ApplyVariantSelector(const Layer &layer, const VariantSelectorMap &vsmap, + Layer *dst, std::string *warn, std::string *err); + +/// +/// Handy version of ApplyVariantSelector. +/// Use same variant name for all variantSets in Prim tree. +/// +bool ApplyVariantSelector(const Layer &layer, const std::string &variant_name, + Layer *dst, std::string *warn, std::string *err); + +/// +/// Implementation of `references` +/// +/// Import `layer` to this PrimSpec. +/// +/// @param[inout] dst PrimSpec to be referenced. +/// @param[in] layer Layer(PrimSpec tree) to reference. +/// @param[in] primPath root Prim path in `layer`. Default = invalid Path = +/// defaultPrim in `layer`. +/// @param[in] layerOffset Layer(PrimSpec tree) to reference. +/// +/// Use `defaultPrim` in `layer` as the root PrimSpec to import +/// +/// +bool ReferenceLayerToPrimSpec(PrimSpec &dst, const Layer &layer, + const Path primPath = Path(), + const LayerOffset layerOffset = LayerOffset()); + +/// +/// Extract Variant information from Layer. +/// +/// Example: +/// +/// { "/cube0" : { "variantSets" : ["colorVariant"], "variants" : { "colorVariant" : "green" } } } +/// +bool ExtractVariants(const Layer &layer, Dictionary *dict, std::string *err); + +/// +/// Extract Variant information from Stage. +/// +bool ExtractVariants(const Stage &stage, Dictionary *dict, std::string *err); + +#if 0 // TODO +/// +/// Implementation of `references` +/// +bool ReferenceLayersToPrimSpec(PrimSpec &dst, const std::vector &layers +#endif + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/crate-format.cc b/contrib/tinyusdz/tinyusdz_repo/src/crate-format.cc new file mode 100644 index 000000000..8ae255277 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/crate-format.cc @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +#if defined(__wasi__) +#else +#include +#endif + +#include "common-macros.inc" +#include "crate-format.hh" +#include "external/mapbox/eternal/include/mapbox/eternal.hpp" +#include "pprinter.hh" +#include "value-types.hh" + +namespace tinyusdz { +namespace crate { + +#if 0 + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +nonstd::expected GetCrateDataType(int32_t type_id) { + + + // TODO: Compile-time map using maxbox/eternal + static std::map table; + DCOUT("type_id = " << type_id); + + if (table.size() == 0) { + // Register data types + + // See /pxr/usd/usd/crateDataTypes.h + +#define ADD_VALUE_TYPE(NAME_STR, TYPE_ID, SUPPORTS_ARRAY) \ + { \ + assert(table.count(static_cast(TYPE_ID)) == 0); \ + table[static_cast(TYPE_ID)] = \ + CrateDataType(NAME_STR, TYPE_ID, SUPPORTS_ARRAY); \ + } + + // (num_string, type_id(in crateData), supports_array) + + // 0 is reserved as `Invalid` type. + ADD_VALUE_TYPE("Invald", CrateDataTypeId::CRATE_DATA_TYPE_INVALID, false) + + // Array types. + ADD_VALUE_TYPE("Bool", CrateDataTypeId::CRATE_DATA_TYPE_BOOL, true) + + ADD_VALUE_TYPE("UChar", CrateDataTypeId::CRATE_DATA_TYPE_UCHAR, true) + ADD_VALUE_TYPE("Int", CrateDataTypeId::CRATE_DATA_TYPE_INT, true) + ADD_VALUE_TYPE("UInt", CrateDataTypeId::CRATE_DATA_TYPE_UINT, true) + ADD_VALUE_TYPE("Int64", CrateDataTypeId::CRATE_DATA_TYPE_INT64, true) + ADD_VALUE_TYPE("UInt64", CrateDataTypeId::CRATE_DATA_TYPE_UINT64, true) + + ADD_VALUE_TYPE("Half", CrateDataTypeId::CRATE_DATA_TYPE_HALF, true) + ADD_VALUE_TYPE("Float", CrateDataTypeId::CRATE_DATA_TYPE_FLOAT, true) + ADD_VALUE_TYPE("Double", CrateDataTypeId::CRATE_DATA_TYPE_DOUBLE, true) + + ADD_VALUE_TYPE("String", CrateDataTypeId::CRATE_DATA_TYPE_STRING, true) + ADD_VALUE_TYPE("Token", CrateDataTypeId::CRATE_DATA_TYPE_TOKEN, true) + ADD_VALUE_TYPE("AssetPath", CrateDataTypeId::CRATE_DATA_TYPE_ASSET_PATH, + true) + + ADD_VALUE_TYPE("Matrix2d", CrateDataTypeId::CRATE_DATA_TYPE_MATRIX2D, true) + ADD_VALUE_TYPE("Matrix3d", CrateDataTypeId::CRATE_DATA_TYPE_MATRIX3D, true) + ADD_VALUE_TYPE("Matrix4d", CrateDataTypeId::CRATE_DATA_TYPE_MATRIX4D, true) + + ADD_VALUE_TYPE("Quatd", CrateDataTypeId::CRATE_DATA_TYPE_QUATD, true) + ADD_VALUE_TYPE("Quatf", CrateDataTypeId::CRATE_DATA_TYPE_QUATF, true) + ADD_VALUE_TYPE("Quath", CrateDataTypeId::CRATE_DATA_TYPE_QUATH, true) + + ADD_VALUE_TYPE("Vec2d", CrateDataTypeId::CRATE_DATA_TYPE_VEC2D, true) + ADD_VALUE_TYPE("Vec2f", CrateDataTypeId::CRATE_DATA_TYPE_VEC2F, true) + ADD_VALUE_TYPE("Vec2h", CrateDataTypeId::CRATE_DATA_TYPE_VEC2H, true) + ADD_VALUE_TYPE("Vec2i", CrateDataTypeId::CRATE_DATA_TYPE_VEC2I, true) + + ADD_VALUE_TYPE("Vec3d", CrateDataTypeId::CRATE_DATA_TYPE_VEC3D, true) + ADD_VALUE_TYPE("Vec3f", CrateDataTypeId::CRATE_DATA_TYPE_VEC3F, true) + ADD_VALUE_TYPE("Vec3h", CrateDataTypeId::CRATE_DATA_TYPE_VEC3H, true) + ADD_VALUE_TYPE("Vec3i", CrateDataTypeId::CRATE_DATA_TYPE_VEC3I, true) + + ADD_VALUE_TYPE("Vec4d", CrateDataTypeId::CRATE_DATA_TYPE_VEC4D, true) + ADD_VALUE_TYPE("Vec4f", CrateDataTypeId::CRATE_DATA_TYPE_VEC4F, true) + ADD_VALUE_TYPE("Vec4h", CrateDataTypeId::CRATE_DATA_TYPE_VEC4H, true) + ADD_VALUE_TYPE("Vec4i", CrateDataTypeId::CRATE_DATA_TYPE_VEC4I, true) + + // Non-array types. + + // + // commented = TODO + // + ADD_VALUE_TYPE("Dictionary", CrateDataTypeId::CRATE_DATA_TYPE_DICTIONARY, + false) + + ADD_VALUE_TYPE("TokenListOp", + CrateDataTypeId::CRATE_DATA_TYPE_TOKEN_LIST_OP, false) + ADD_VALUE_TYPE("StringListOp", + CrateDataTypeId::CRATE_DATA_TYPE_STRING_LIST_OP, false) + ADD_VALUE_TYPE("PathListOp", CrateDataTypeId::CRATE_DATA_TYPE_PATH_LIST_OP, + false) + ADD_VALUE_TYPE("ReferenceListOp", + CrateDataTypeId::CRATE_DATA_TYPE_REFERENCE_LIST_OP, false) + ADD_VALUE_TYPE("IntListOp", CrateDataTypeId::CRATE_DATA_TYPE_INT_LIST_OP, + false) + ADD_VALUE_TYPE("Int64ListOp", + CrateDataTypeId::CRATE_DATA_TYPE_INT64_LIST_OP, false) + ADD_VALUE_TYPE("UIntListOp", CrateDataTypeId::CRATE_DATA_TYPE_UINT_LIST_OP, + false) + ADD_VALUE_TYPE("UInt64ListOp", + CrateDataTypeId::CRATE_DATA_TYPE_UINT64_LIST_OP, false) + + ADD_VALUE_TYPE("PathVector", CrateDataTypeId::CRATE_DATA_TYPE_PATH_VECTOR, + false) + ADD_VALUE_TYPE("TokenVector", CrateDataTypeId::CRATE_DATA_TYPE_TOKEN_VECTOR, + false) + + ADD_VALUE_TYPE("Specifier", CrateDataTypeId::CRATE_DATA_TYPE_SPECIFIER, + false) + ADD_VALUE_TYPE("Permission", CrateDataTypeId::CRATE_DATA_TYPE_PERMISSION, + false) + ADD_VALUE_TYPE("Variability", CrateDataTypeId::CRATE_DATA_TYPE_VARIABILITY, + false) + + ADD_VALUE_TYPE("VariantSelectionMap", + CrateDataTypeId::CRATE_DATA_TYPE_VARIANT_SELECTION_MAP, + false) + ADD_VALUE_TYPE("TimeSamples", CrateDataTypeId::CRATE_DATA_TYPE_TIME_SAMPLES, + false) + ADD_VALUE_TYPE("Payload", CrateDataTypeId::CRATE_DATA_TYPE_PAYLOAD, false) + ADD_VALUE_TYPE("DoubleVector", + CrateDataTypeId::CRATE_DATA_TYPE_DOUBLE_VECTOR, false) + ADD_VALUE_TYPE("LayerOffsetVector", + CrateDataTypeId::CRATE_DATA_TYPE_LAYER_OFFSET_VECTOR, false) + ADD_VALUE_TYPE("StringVector", + CrateDataTypeId::CRATE_DATA_TYPE_STRING_VECTOR, false) + ADD_VALUE_TYPE("ValueBlock", CrateDataTypeId::CRATE_DATA_TYPE_VALUE_BLOCK, + false) + ADD_VALUE_TYPE("Value", CrateDataTypeId::CRATE_DATA_TYPE_VALUE, false) + ADD_VALUE_TYPE("UnregisteredValue", + CrateDataTypeId::CRATE_DATA_TYPE_UNREGISTERED_VALUE, false) + ADD_VALUE_TYPE("UnregisteredValueListOp", + CrateDataTypeId::CRATE_DATA_TYPE_UNREGISTERED_VALUE_LIST_OP, + false) + ADD_VALUE_TYPE("PayloadListOp", + CrateDataTypeId::CRATE_DATA_TYPE_PAYLOAD_LIST_OP, false) + ADD_VALUE_TYPE("TimeCode", CrateDataTypeId::CRATE_DATA_TYPE_TIME_CODE, true) + } +#undef ADD_VALUE_TYPE + + if (type_id < 0) { + return nonstd::make_unexpected("Unknown type id: " + + std::to_string(type_id)); + } + + if (!table.count(static_cast(type_id))) { + // Invalid or unsupported. + return nonstd::make_unexpected("Unknown or unspported type id: " + + std::to_string(type_id)); + } + + return table.at(static_cast(type_id)); +} +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#else + +nonstd::expected GetCrateDataType(int32_t type_id) { + // See /pxr/usd/usd/crateDataTypes.h + + // TODO: Use type name in value-types.hh and prim-types.hh? + MAPBOX_ETERNAL_CONSTEXPR const auto tymap = + mapbox::eternal::map({ + {CrateDataTypeId::CRATE_DATA_TYPE_INVALID, "Invalid"}, // 0 + {CrateDataTypeId::CRATE_DATA_TYPE_BOOL, "Bool"}, + {CrateDataTypeId::CRATE_DATA_TYPE_UCHAR, "UChar"}, + {CrateDataTypeId::CRATE_DATA_TYPE_INT, "Int"}, + {CrateDataTypeId::CRATE_DATA_TYPE_UINT, "UInt"}, + {CrateDataTypeId::CRATE_DATA_TYPE_INT64, "Int64"}, + {CrateDataTypeId::CRATE_DATA_TYPE_UINT64, "UInt64"}, + + {CrateDataTypeId::CRATE_DATA_TYPE_HALF, "Half"}, + {CrateDataTypeId::CRATE_DATA_TYPE_FLOAT, "Float"}, + {CrateDataTypeId::CRATE_DATA_TYPE_DOUBLE, "Double"}, + + {CrateDataTypeId::CRATE_DATA_TYPE_STRING, "String"}, + {CrateDataTypeId::CRATE_DATA_TYPE_TOKEN, "Token"}, + {CrateDataTypeId::CRATE_DATA_TYPE_ASSET_PATH, "AssetPath"}, + + {CrateDataTypeId::CRATE_DATA_TYPE_MATRIX2D, "Matrix2d"}, + {CrateDataTypeId::CRATE_DATA_TYPE_MATRIX3D, "Matrix3d"}, + {CrateDataTypeId::CRATE_DATA_TYPE_MATRIX4D, "Matrix4d"}, + + {CrateDataTypeId::CRATE_DATA_TYPE_QUATD, "Quatd"}, + {CrateDataTypeId::CRATE_DATA_TYPE_QUATF, "Quatf"}, + {CrateDataTypeId::CRATE_DATA_TYPE_QUATH, "Quath"}, + + {CrateDataTypeId::CRATE_DATA_TYPE_VEC2D, "Vec2d"}, + {CrateDataTypeId::CRATE_DATA_TYPE_VEC2F, "Vec2f"}, + {CrateDataTypeId::CRATE_DATA_TYPE_VEC2H, "Vec2h"}, + {CrateDataTypeId::CRATE_DATA_TYPE_VEC2I, "Vec2i"}, + + {CrateDataTypeId::CRATE_DATA_TYPE_VEC3D, "Vec3d"}, + {CrateDataTypeId::CRATE_DATA_TYPE_VEC3F, "Vec3f"}, + {CrateDataTypeId::CRATE_DATA_TYPE_VEC3H, "Vec3h"}, + {CrateDataTypeId::CRATE_DATA_TYPE_VEC3I, "Vec3i"}, + + {CrateDataTypeId::CRATE_DATA_TYPE_VEC4D, "Vec4d"}, + {CrateDataTypeId::CRATE_DATA_TYPE_VEC4F, "Vec4f"}, + {CrateDataTypeId::CRATE_DATA_TYPE_VEC4H, "Vec4h"}, + {CrateDataTypeId::CRATE_DATA_TYPE_VEC4I, "Vec4i"}, + + // Non-array types. + {CrateDataTypeId::CRATE_DATA_TYPE_DICTIONARY, "Dictionary"}, + {CrateDataTypeId::CRATE_DATA_TYPE_TOKEN_LIST_OP, "TokenListOp"}, + {CrateDataTypeId::CRATE_DATA_TYPE_STRING_LIST_OP, "StringListOp"}, + {CrateDataTypeId::CRATE_DATA_TYPE_PATH_LIST_OP, "PathListOp"}, + {CrateDataTypeId::CRATE_DATA_TYPE_REFERENCE_LIST_OP, + "ReferenceListOp"}, + {CrateDataTypeId::CRATE_DATA_TYPE_INT_LIST_OP, "IntListOp"}, + {CrateDataTypeId::CRATE_DATA_TYPE_INT64_LIST_OP, "Int64ListOp"}, + {CrateDataTypeId::CRATE_DATA_TYPE_UINT_LIST_OP, "UIntListOp"}, + {CrateDataTypeId::CRATE_DATA_TYPE_UINT64_LIST_OP, "UInt64ListOp"}, + + {CrateDataTypeId::CRATE_DATA_TYPE_PATH_VECTOR, "PathVector"}, + {CrateDataTypeId::CRATE_DATA_TYPE_TOKEN_VECTOR, "TokenVector"}, + {CrateDataTypeId::CRATE_DATA_TYPE_SPECIFIER, "Specifier"}, + {CrateDataTypeId::CRATE_DATA_TYPE_PERMISSION, "Permission"}, + {CrateDataTypeId::CRATE_DATA_TYPE_VARIABILITY, "Variability"}, + + {CrateDataTypeId::CRATE_DATA_TYPE_VARIANT_SELECTION_MAP, + "VariantSelectionMap"}, + {CrateDataTypeId::CRATE_DATA_TYPE_TIME_SAMPLES, "TimeSamples"}, + {CrateDataTypeId::CRATE_DATA_TYPE_PAYLOAD, "Payload"}, + {CrateDataTypeId::CRATE_DATA_TYPE_DOUBLE_VECTOR, "DoubleVector"}, + {CrateDataTypeId::CRATE_DATA_TYPE_LAYER_OFFSET_VECTOR, + "LayerOffsetVector"}, + {CrateDataTypeId::CRATE_DATA_TYPE_STRING_VECTOR, "StringVector"}, + {CrateDataTypeId::CRATE_DATA_TYPE_VALUE_BLOCK, "ValueBlock"}, + {CrateDataTypeId::CRATE_DATA_TYPE_VALUE, "Value"}, + {CrateDataTypeId::CRATE_DATA_TYPE_UNREGISTERED_VALUE, + "UnregisteredValue"}, + {CrateDataTypeId::CRATE_DATA_TYPE_UNREGISTERED_VALUE_LIST_OP, + "UnregisteredValueListOp"}, + {CrateDataTypeId::CRATE_DATA_TYPE_PAYLOAD_LIST_OP, "PayloadListOp"}, + {CrateDataTypeId::CRATE_DATA_TYPE_TIME_CODE, "TimeCode"}, + }); + + // List up `supports array` type. + // TODO: Use compile-time `set` + MAPBOX_ETERNAL_CONSTEXPR const auto arrmap = + mapbox::eternal::map({ + {CrateDataTypeId::CRATE_DATA_TYPE_BOOL, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_UCHAR, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_INT, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_UINT, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_INT64, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_UINT64, true}, + + {CrateDataTypeId::CRATE_DATA_TYPE_HALF, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_FLOAT, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_DOUBLE, true}, + + {CrateDataTypeId::CRATE_DATA_TYPE_STRING, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_TOKEN, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_ASSET_PATH, true}, + + {CrateDataTypeId::CRATE_DATA_TYPE_MATRIX2D, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_MATRIX3D, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_MATRIX4D, true}, + + {CrateDataTypeId::CRATE_DATA_TYPE_QUATD, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_QUATF, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_QUATH, true}, + + {CrateDataTypeId::CRATE_DATA_TYPE_VEC2D, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_VEC2F, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_VEC2H, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_VEC2I, true}, + + {CrateDataTypeId::CRATE_DATA_TYPE_VEC3D, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_VEC3F, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_VEC3H, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_VEC3I, true}, + + {CrateDataTypeId::CRATE_DATA_TYPE_VEC4D, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_VEC4F, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_VEC4H, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_VEC4I, true}, + {CrateDataTypeId::CRATE_DATA_TYPE_TIME_CODE, true}, + }); + + if (type_id < 0) { + return nonstd::make_unexpected("Unknown type id: " + + std::to_string(type_id)); + } + + auto tyret = tymap.find(static_cast(type_id)); + + if (tyret == tymap.end()) { + // Invalid or unsupported. + return nonstd::make_unexpected("Unknown or unspported type id: " + + std::to_string(type_id)); + } + + bool supports_array = arrmap.count(static_cast(type_id)); + + CrateDataType dst(tyret->second.data(), static_cast(type_id), + supports_array); + + return std::move(dst); +} +#endif + +std::string GetCrateDataTypeRepr(CrateDataType dty) { + auto tyRet = GetCrateDataType(static_cast(dty.dtype_id)); + if (!tyRet) { + return "[Invalid]"; + } + + const CrateDataType ty = tyRet.value(); + + std::stringstream ss; + ss << "CrateDataType: " << ty.name << "(" + << static_cast(ty.dtype_id) + << "), supports_array = " << ty.supports_array; + return ss.str(); +} + +std::string GetCrateDataTypeName(int32_t type_id) { + auto tyRet = GetCrateDataType(type_id); + if (!tyRet) { + return "[Invalid]"; + } + + const CrateDataType dty = tyRet.value(); + return dty.name; +} + +std::string GetCrateDataTypeName(CrateDataTypeId did) { + return GetCrateDataTypeName(static_cast(did)); +} + +// std::string CrateValue::GetTypeName() const { return value_.type_name(); } +// uint32_t CrateValue::GetTypeId() const { return value_.type_id(); } + +} // namespace crate +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/crate-format.hh b/contrib/tinyusdz/tinyusdz_repo/src/crate-format.hh new file mode 100644 index 000000000..6646e5a5f --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/crate-format.hh @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +// USDC(CrateFile) format +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "prim-types.hh" +#include "value-types.hh" + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#include "nonstd/expected.hpp" + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +namespace tinyusdz { +namespace crate { + +constexpr size_t kMinCompressedArraySize = 16; +constexpr size_t kSectionNameMaxLength = 15; + +constexpr char kTokenVector[] = "TokenVector"; +constexpr char kStringVector[] = "StringVector"; +constexpr char kPathVector[] = "PathVector"; + +// crate data type +// id must be identitical to /pxr/usd/usd/crateDataType.h +enum class CrateDataTypeId { + CRATE_DATA_TYPE_INVALID = 0, + + CRATE_DATA_TYPE_BOOL = 1, + CRATE_DATA_TYPE_UCHAR = 2, + CRATE_DATA_TYPE_INT = 3, + CRATE_DATA_TYPE_UINT = 4, + CRATE_DATA_TYPE_INT64 = 5, + CRATE_DATA_TYPE_UINT64 = 6, + + CRATE_DATA_TYPE_HALF = 7, + CRATE_DATA_TYPE_FLOAT = 8, + CRATE_DATA_TYPE_DOUBLE = 9, + + CRATE_DATA_TYPE_STRING = 10, + CRATE_DATA_TYPE_TOKEN = 11, + CRATE_DATA_TYPE_ASSET_PATH = 12, + + CRATE_DATA_TYPE_MATRIX2D = 13, + CRATE_DATA_TYPE_MATRIX3D = 14, + CRATE_DATA_TYPE_MATRIX4D = 15, + + CRATE_DATA_TYPE_QUATD = 16, + CRATE_DATA_TYPE_QUATF = 17, + CRATE_DATA_TYPE_QUATH = 18, + + CRATE_DATA_TYPE_VEC2D = 19, + CRATE_DATA_TYPE_VEC2F = 20, + CRATE_DATA_TYPE_VEC2H = 21, + CRATE_DATA_TYPE_VEC2I = 22, + + CRATE_DATA_TYPE_VEC3D = 23, + CRATE_DATA_TYPE_VEC3F = 24, + CRATE_DATA_TYPE_VEC3H = 25, + CRATE_DATA_TYPE_VEC3I = 26, + + CRATE_DATA_TYPE_VEC4D = 27, + CRATE_DATA_TYPE_VEC4F = 28, + CRATE_DATA_TYPE_VEC4H = 29, + CRATE_DATA_TYPE_VEC4I = 30, + + CRATE_DATA_TYPE_DICTIONARY = 31, + CRATE_DATA_TYPE_TOKEN_LIST_OP = 32, + CRATE_DATA_TYPE_STRING_LIST_OP = 33, + CRATE_DATA_TYPE_PATH_LIST_OP = 34, + CRATE_DATA_TYPE_REFERENCE_LIST_OP = 35, + CRATE_DATA_TYPE_INT_LIST_OP = 36, + CRATE_DATA_TYPE_INT64_LIST_OP = 37, + CRATE_DATA_TYPE_UINT_LIST_OP = 38, + CRATE_DATA_TYPE_UINT64_LIST_OP = 39, + + CRATE_DATA_TYPE_PATH_VECTOR = 40, + CRATE_DATA_TYPE_TOKEN_VECTOR = 41, + + CRATE_DATA_TYPE_SPECIFIER = 42, + CRATE_DATA_TYPE_PERMISSION = 43, + CRATE_DATA_TYPE_VARIABILITY = 44, + + CRATE_DATA_TYPE_VARIANT_SELECTION_MAP = 45, + CRATE_DATA_TYPE_TIME_SAMPLES = 46, + CRATE_DATA_TYPE_PAYLOAD = 47, + CRATE_DATA_TYPE_DOUBLE_VECTOR = 48, + CRATE_DATA_TYPE_LAYER_OFFSET_VECTOR = 49, + CRATE_DATA_TYPE_STRING_VECTOR = 50, + CRATE_DATA_TYPE_VALUE_BLOCK = 51, + CRATE_DATA_TYPE_VALUE = 52, // Contains ValueRep + CRATE_DATA_TYPE_UNREGISTERED_VALUE = 53, // String or Dict + CRATE_DATA_TYPE_UNREGISTERED_VALUE_LIST_OP = 54, + CRATE_DATA_TYPE_PAYLOAD_LIST_OP = 55, + CRATE_DATA_TYPE_TIME_CODE = 56, + + NumDataTypes // terminator +}; + +class CrateDataType +{ + public: + CrateDataType() = default; + + CrateDataType(const char *s, CrateDataTypeId did, bool a) + : name(s), dtype_id(did), supports_array(a) { + } + + CrateDataType(const CrateDataType &rhs) = default; + CrateDataType &operator=(const CrateDataType&rhs) = default; + + const char *name{nullptr}; // name of CrateDatatType. Constant symbol. TODO: Use string_view. + CrateDataTypeId dtype_id{CrateDataTypeId::CRATE_DATA_TYPE_INVALID}; + bool supports_array{false}; +}; + +std::string GetCrateDataTypeRepr(CrateDataType dty); // for debug cout + +nonstd::expected GetCrateDataType(int32_t type_id); +std::string GetCrateDataTypeName(int32_t type_id); +std::string GetCrateDataTypeName(CrateDataTypeId type_id); + + + +// -- from USD ---------------------------------------------------------------- + +// +// Copyright 2016 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "Apache License") +// with the following modification; you may not use this file except in +// compliance with the Apache License and the following modification to it: +// Section 6. Trademarks. is deleted and replaced with: +// +// 6. Trademarks. This License does not grant permission to use the trade +// names, trademarks, service marks, or product names of the Licensor +// and its affiliates, except as required to comply with Section 4(c) of +// the License and to reproduce the content of the NOTICE file. +// +// You may obtain a copy of the Apache License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the Apache License with the above modification is +// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the Apache License for the specific +// language governing permissions and limitations under the Apache License. + +// Index base class. Used to index various tables. Deriving adds some +// type-safety so we don't accidentally use one kind of index with the wrong +// kind of table. +struct Index { + Index() : value(~0u) {} + explicit Index(uint32_t v) : value(v) {} + bool operator==(const Index &other) const { return value == other.value; } + bool operator!=(const Index &other) const { return !(*this == other); } + bool operator<(const Index &other) const { return value < other.value; } + uint32_t value; +}; + +// Value in file representation. Consists of a 2 bytes of type information +// (type enum value, array bit, and inlined-value bit) and 6 bytes of data. +// If possible, we attempt to store certain values directly in the local +// data, such as ints, floats, enums, and special-case values of other types +// (zero vectors, identity matrices, etc). For values that aren't stored +// inline, the 6 data bytes are the offset from the start of the file to the +// value's location. +struct ValueRep { + friend class CrateFile; + + ValueRep() = default; + + explicit constexpr ValueRep(uint64_t d) : data(d) {} + + constexpr ValueRep(int32_t t, bool isInlined, bool isArray, uint64_t payload) + : data(Combine(t, isInlined, isArray, payload)) {} + + static const uint64_t IsArrayBit_ = 1ull << 63; + static const uint64_t IsInlinedBit_ = 1ull << 62; + static const uint64_t IsCompressedBit_ = 1ull << 61; + + static const uint64_t PayloadMask_ = ((1ull << 48) - 1); + + inline bool IsArray() const { return data & IsArrayBit_; } + inline void SetIsArray() { data |= IsArrayBit_; } + + inline bool IsInlined() const { return data & IsInlinedBit_; } + inline void SetIsInlined() { data |= IsInlinedBit_; } + + inline bool IsCompressed() const { return data & IsCompressedBit_; } + inline void SetIsCompressed() { data |= IsCompressedBit_; } + + inline int32_t GetType() const { + return static_cast((data >> 48) & 0xFF); + } + inline void SetType(int32_t t) { + data &= ~(0xFFull << 48); // clear type byte in data. + data |= (static_cast(t) << 48); // set it. + } + + inline uint64_t GetPayload() const { return data & PayloadMask_; } + + inline void SetPayload(uint64_t payload) { + data &= ~PayloadMask_; // clear existing payload. + data |= payload & PayloadMask_; + } + + inline uint64_t GetData() const { return data; } + + bool operator==(ValueRep other) const { return data == other.data; } + bool operator!=(ValueRep other) const { return !(*this == other); } + + // friend inline size_t hash_value(ValueRep v) { + // return static_cast(v.data); + //} + + std::string GetStringRepr() const { + std::stringstream ss; + ss << "ty: " << static_cast(GetType()) << "(" << GetCrateDataTypeName(GetType()) << "), isArray: " << IsArray() + << ", isInlined: " << IsInlined() << ", isCompressed: " << IsCompressed() + << ", payload: " << GetPayload(); + + return ss.str(); + } + + private: + static constexpr uint64_t Combine(int32_t t, bool isInlined, bool isArray, + uint64_t payload) { + return (isArray ? IsArrayBit_ : 0) | (isInlined ? IsInlinedBit_ : 0) | + (static_cast(t) << 48) | (payload & PayloadMask_); + } + + uint64_t data; +}; + +struct TokenIndex : Index { using Index::Index; }; +struct StringIndex : Index { using Index::Index; }; +struct FieldIndex : Index { using Index::Index; }; +struct FieldSetIndex : Index { using Index::Index; }; +struct PathIndex : Index { using Index::Index; }; + +// ---------------------------------------------------------------------------- + + +struct Field { + TokenIndex token_index; + ValueRep value_rep; +}; + +// +// Spec describes the relation of a path(i.e. node) and field(e.g. vertex data) +// +struct Spec { + Index path_index; + Index fieldset_index; + SpecType spec_type; // Must be 32bit +}; + +static_assert(sizeof(Spec) == (4 * 3), "sizeof(Spec) must be 12"); + +struct Section { + Section() { memset(this, 0, sizeof(*this)); } + Section(char const *name, int64_t start, int64_t size); + char name[kSectionNameMaxLength + 1]; + int64_t start, size; // byte offset to section info and its data size +}; + +// For unordered_map + +// https://stackoverflow.com/questions/8513911/how-to-create-a-good-hash-combine-with-64-bit-output-inspired-by-boosthash-co +// From CityHash code. + +template +inline void hash_combine_impl32(std::size_t &seed, const T &v) +{ + // Use boost version. + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +template +inline void hash_combine(std::size_t &seed, const T &v) { +#if defined(__wasi__) // 32bit platform + hash_combine_impl32(seed, v); +#else + if (sizeof(std::size_t) == 4) { + hash_combine_impl32(seed, v); + } else { + // Assume 64bit + std::hash hasher; + const uint64_t kMul = 0x9ddfea08eb382d69ULL; + uint64_t a = (hasher(v) ^ seed) * kMul; + a ^= (a >> 47); + uint64_t b = (seed ^ a) * kMul; + b ^= (b >> 47); + seed = size_t(b * kMul); + } +#endif +} + +struct PathHasher { + size_t operator()(const Path &path) const { + size_t seed = std::hash()(path.prim_part()); + hash_combine(seed, std::hash()(path.prop_part())); + //hash_combine(seed, std::hash()(path.GetLocalPart())); + hash_combine(seed, std::hash()(path.is_valid())); + + return seed; + } +}; + +struct PathKeyEqual { + bool operator()(const Path &lhs, const Path &rhs) const { + bool ret = lhs.prim_part() == rhs.prim_part(); + ret &= lhs.prop_part() == rhs.prop_part(); + //ret &= lhs.GetLocalPart() == rhs.GetLocalPart(); + ret &= lhs.is_valid() == rhs.is_valid(); + + return ret; + } +}; + +struct FieldHasher { + size_t operator()(const Field &field) const { + size_t seed = std::hash()(field.token_index.value); + hash_combine(seed, std::hash()(field.value_rep.GetData())); + + return seed; + } +}; + +struct FieldKeyEqual { + bool operator()(const Field &lhs, const Field &rhs) const { + bool ret = lhs.token_index == rhs.token_index; + ret &= lhs.value_rep == rhs.value_rep; + + return ret; + } +}; + +struct FieldSetHasher { + size_t operator()(const std::vector &fieldset) const { + if (fieldset.empty()) { + return 0; + } + + size_t seed = std::hash()(fieldset[0].value); + for (size_t i = 1; i < fieldset.size(); i++) { + hash_combine(seed, std::hash()(fieldset[i].value)); + } + + return seed; + } +}; + +// +// TOC = list of sections. +// +struct TableOfContents { + // Section const *GetSection(SectionName) const; + // int64_t GetMinimumSectionStart() const; + std::vector
sections; +}; + +// TODO: Use PrimVar? +class CrateValue { + public: + //typedef std::map Dictionary; + + //std::string GetTypeName() const; + //uint32_t GetTypeId() const; + +#define SET_TYPE_SCALAR(__ty) void Set(const __ty& v) { value_ = v; } +#define SET_TYPE_1D(__ty) void Set(const std::vector<__ty> &v) { value_ = v; } + +#define SET_TYPE_LIST(__FUNC) \ + __FUNC(int64_t) \ + __FUNC(uint64_t) \ + __FUNC(value::half) \ + __FUNC(value::half2) \ + __FUNC(value::half3) \ + __FUNC(value::half4) \ + __FUNC(int) \ + __FUNC(value::int2) \ + __FUNC(value::int3) \ + __FUNC(value::int4) \ + __FUNC(uint32_t) \ + __FUNC(value::uint2) \ + __FUNC(value::uint3) \ + __FUNC(value::uint4) \ + __FUNC(float) \ + __FUNC(value::float2) \ + __FUNC(value::float3) \ + __FUNC(value::float4) \ + __FUNC(double) \ + __FUNC(value::double2) \ + __FUNC(value::double3) \ + __FUNC(value::double4) \ + __FUNC(value::quath) \ + __FUNC(value::quatf) \ + __FUNC(value::quatd) \ + __FUNC(value::matrix2d) \ + __FUNC(value::matrix3d) \ + __FUNC(value::matrix4d) \ + __FUNC(value::AssetPath) \ + __FUNC(value::token) \ + __FUNC(std::string) + + + // Note: Use bool and std::vector as-is in C++ layer, but its serialized as 8bit in Crate binary. + SET_TYPE_SCALAR(bool) + SET_TYPE_1D(bool) + + SET_TYPE_SCALAR(Specifier) + SET_TYPE_SCALAR(Permission) + SET_TYPE_SCALAR(Variability) + SET_TYPE_SCALAR(value::dict) + + SET_TYPE_SCALAR(value::ValueBlock) + + SET_TYPE_SCALAR(ListOp) + SET_TYPE_SCALAR(ListOp) + SET_TYPE_SCALAR(ListOp) + SET_TYPE_SCALAR(ListOp) + SET_TYPE_SCALAR(ListOp) + SET_TYPE_SCALAR(ListOp) + SET_TYPE_SCALAR(ListOp) + SET_TYPE_SCALAR(ListOp) + SET_TYPE_SCALAR(ListOp) + + SET_TYPE_SCALAR(std::vector) + // vector is defined in SET_TYPE_LIST(SET_TYPE_1D) + //SET_TYPE_SCALAR(std::vector) + SET_TYPE_SCALAR(std::vector) + SET_TYPE_SCALAR(Payload) + SET_TYPE_SCALAR(VariantSelectionMap) + + SET_TYPE_SCALAR(value::TimeSamples) + SET_TYPE_SCALAR(CustomDataType) // for (type-restricted) dist + + SET_TYPE_LIST(SET_TYPE_SCALAR) + + + SET_TYPE_LIST(SET_TYPE_1D) + +#if 0 // TODO: Unsafe so Remove + // Useful function to retrieve concrete value with type T. + // Undefined behavior(usually will triger segmentation fault) when + // type-mismatch. (We don't throw exception) + template + const T value() const { + //return (*reinterpret_cast(value_.value())); + //return linb::any_cast(value_); + return value_.value(); + } +#endif + + // Type-safe way to get concrete value. + template + nonstd::optional get_value() const { + return value_.get_value(); + } + + // Return null when type-mismatch + template + const T *as() const { + return value_.as(); + } + + std::string type_name() const { + return value_.type_name(); + } + + uint32_t type_id() const { + return value_.type_id(); + } + + const value::Value &get_raw() const { + return value_; + } + + private: + value::Value value_; +}; + +// In-memory storage for a single "spec" -- prim, property, etc. +using FieldValuePair = std::pair; +using FieldValuePairVector = std::vector; + +struct StdHashWrapper { + template + inline size_t operator()(const T &val) const { + return std::hash()(val); + } +}; + + +} // namespace crate + +namespace value { + +#include "define-type-trait.inc" + +// synonym to `value::dict` +//DEFINE_TYPE_TRAIT(crate::CrateValue::Dictionary, "dict", TYPE_ID_DICT, 1); + +#undef DEFINE_TYPE_TRAIT +#undef DEFINE_ROLE_TYPE_TRAIT + +} // namespace value + +} // namespace tinyusdz + + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/crate-pprint.cc b/contrib/tinyusdz/tinyusdz_repo/src/crate-pprint.cc new file mode 100644 index 000000000..01d0a4bd6 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/crate-pprint.cc @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +#include "crate-pprint.hh" + +#include + +namespace std { + +std::ostream &operator<<(std::ostream &os, const tinyusdz::crate::Index &i) { + os << std::to_string(i.value); + return os; +} + +} // namespace std + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/crate-pprint.hh b/contrib/tinyusdz/tinyusdz_repo/src/crate-pprint.hh new file mode 100644 index 000000000..a52cf6f2a --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/crate-pprint.hh @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +#pragma once + +#include + +#include "crate-format.hh" + +namespace std { + +std::ostream &operator<<(std::ostream &os, const tinyusdz::crate::Index &i); + +} // namespace std + +namespace tinyusdz { + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/crate-reader.cc b/contrib/tinyusdz/tinyusdz_repo/src/crate-reader.cc new file mode 100644 index 000000000..2cd57268c --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/crate-reader.cc @@ -0,0 +1,6281 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022-2022 Syoyo Fujita. +// Copyright 2023-Present Light Transport Entertainment Inc. +// +// Crate(binary format) reader +// +// +// TODO: +// - [] Unify BuildDecompressedPathsImpl and BuildNodeHierarchy + +#ifdef _MSC_VER +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + +#include "crate-reader.hh" + +#ifdef __wasi__ +#else +#include +#endif + +#include +#include + +#include "crate-format.hh" +#include "crate-pprint.hh" +#include "integerCoding.h" +#include "lz4-compression.hh" +#include "path-util.hh" +#include "pprinter.hh" +#include "prim-types.hh" +#include "stream-reader.hh" +#include "tinyusdz.hh" +#include "value-pprint.hh" +#include "value-types.hh" +#include "tiny-format.hh" +#include "str-util.hh" + +// +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#include "nonstd/expected.hpp" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// + +#include "common-macros.inc" + +namespace tinyusdz { +namespace crate { + +//constexpr auto kTypeName = "typeName"; +//constexpr auto kToken = "Token"; +//constexpr auto kDefault = "default"; + +#define kTag "[Crate]" + +#define CHECK_MEMORY_USAGE(__nbytes) do { \ + _memoryUsage += (__nbytes); \ + if (_memoryUsage > _config.maxMemoryBudget) { \ + PUSH_ERROR_AND_RETURN_TAG(kTag, "Reached to max memory budget."); \ + } \ + } while(0) + +#define REDUCE_MEMORY_USAGE(__nbytes) do { \ + if (_memoryUsage < (__nbytes)) { \ + _memoryUsage -= (__nbytes); \ + } \ + } while(0) + + + +#define VERSION_LESS_THAN_0_8_0(__version) ((_version[0] == 0) && (_version[1] < 7)) + +// +// -- +// +CrateReader::CrateReader(StreamReader *sr, const CrateReaderConfig &config) : _sr(sr), _impl(nullptr) { + _config = config; + if (_config.numThreads == -1) { +#if defined(__wasi__) +#else + _config.numThreads = (std::max)(1, int(std::thread::hardware_concurrency())); + PUSH_WARN("# of thread to use: " << std::to_string(_config.numThreads)); +#endif + } + + +#if defined(__wasi__) + PUSH_WARN("Threading is disabled for WASI build."); + _config.numThreads = 1; +#else + + // Limit to 1024 threads. + _config.numThreads = (std::min)(1024, _config.numThreads); +#endif + + //_impl = new Impl(); + +} + +CrateReader::~CrateReader() { + //delete _impl; + //_impl = nullptr; +} + +std::string CrateReader::GetError() { return _err; } + +std::string CrateReader::GetWarning() { return _warn; } + +bool CrateReader::HasField(const std::string &key) const { + // Simple linear search + for (const auto &field : _fields) { + if (auto fv = GetToken(field.token_index)) { + if (fv.value().str().compare(key) == 0) { + return true; + } + } + } + return false; +} + +nonstd::optional CrateReader::GetField(crate::Index index) const { + + if (index.value < _fields.size()) { + return _fields[index.value]; + } else { + return nonstd::nullopt; + } +} + +const nonstd::optional CrateReader::GetToken( + crate::Index token_index) const { + if (token_index.value < _tokens.size()) { + return _tokens[token_index.value]; + } else { + return nonstd::nullopt; + } +} + +// Get string token from string index. +const nonstd::optional CrateReader::GetStringToken( + crate::Index string_index) const { + + if (string_index.value < _string_indices.size()) { + crate::Index s_idx = _string_indices[string_index.value]; + return GetToken(s_idx); + } else { + PUSH_ERROR("String index out of range: " + + std::to_string(string_index.value)); + return value::token(); + } +} + +nonstd::optional CrateReader::GetPath(crate::Index index) const { + + if (index.value < _paths.size()) { + // ok + } else { + return nonstd::nullopt; + } + + return _paths[index.value]; +} + +nonstd::optional CrateReader::GetElementPath(crate::Index index) const { + if (index.value < _elemPaths.size()) { + // ok + } else { + return nonstd::nullopt; + } + + return _elemPaths[index.value]; +} + +nonstd::optional CrateReader::GetPathString( + crate::Index index) const { + if (index.value < _paths.size()) { + // ok + } else { + return nonstd::nullopt; + } + + const Path &p = _paths[index.value]; + + return p.full_path_name(); +} + +bool CrateReader::ReadIndex(crate::Index *i) { + // string is serialized as StringIndex + uint32_t value; + if (!_sr->read4(&value)) { + PUSH_ERROR("Failed to read Index"); + return false; + } + + CHECK_MEMORY_USAGE(sizeof(uint32_t)); + + (*i) = crate::Index(value); + return true; +} + +bool CrateReader::ReadIndices(std::vector *indices) { + uint64_t n; + if (!_sr->read8(&n)) { + return false; + } + + if (n > _config.maxNumIndices) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Too many indices."); + } + + if (n == 0) { + return true; + } + + DCOUT("ReadIndices: n = " << n); + + size_t datalen = size_t(n) * sizeof(crate::Index); + + if (datalen > _sr->size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Indices data exceeds USDC size."); + } + + CHECK_MEMORY_USAGE(datalen); + + indices->resize(size_t(n)); + + if (datalen != _sr->read(datalen, datalen, + reinterpret_cast(indices->data()))) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read Indices array."); + } + + return true; +} + +bool CrateReader::ReadString(std::string *s) { + // string is serialized as StringIndex + crate::Index string_index; + if (!ReadIndex(&string_index)) { + PUSH_ERROR("Failed to read Index for string data."); + return false; + } + + if (auto tok = GetStringToken(string_index)) { + (*s) = tok.value().str(); + CHECK_MEMORY_USAGE(s->size()); + return true; + } + + + PUSH_ERROR("Invalid StringIndex."); + return false; +} + +nonstd::optional CrateReader::GetSpecString( + crate::Index index) const { + if (index.value < _specs.size()) { + // ok + } else { + return nonstd::nullopt; + } + + const crate::Spec &spec = _specs[index.value]; + + if (auto pathv = GetPathString(spec.path_index)) { + std::string path_str = pathv.value(); + std::string specty_str = to_string(spec.spec_type); + + return "[Spec] path: " + path_str + + ", fieldset id: " + std::to_string(spec.fieldset_index.value) + + ", spec_type: " + specty_str; + } + + return nonstd::nullopt; +} + +bool CrateReader::ReadValueRep(crate::ValueRep *rep) { + if (!_sr->read8(reinterpret_cast(rep))) { + PUSH_ERROR("Failed to read ValueRep."); + return false; + } + + CHECK_MEMORY_USAGE(sizeof(uint64_t)); + + DCOUT("ValueRep value = " << rep->GetData()); + + return true; +} + +template +bool CrateReader::ReadCompressedInts(Int *out, + size_t num_ints) { + if (num_ints > _config.maxInts) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("# of ints {} too large. maxInts is set to {}", num_ints, _config.maxInts)); + } + + using Compressor = + typename std::conditional::type; + + + // TODO: Read compressed data from _sr directly + size_t compBufferSize = Compressor::GetCompressedBufferSize(num_ints); + CHECK_MEMORY_USAGE(compBufferSize); + + uint64_t compSize; + if (!_sr->read8(&compSize)) { + return false; + } + + if (compSize > compBufferSize) { + // Truncate + // TODO: return error? + compSize = compBufferSize; + } + + if (compSize > _sr->size()) { + return false; + } + + if (compSize < 4) { + // Too small + return false; + } + + std::vector compBuffer; + compBuffer.resize(compBufferSize); + if (!_sr->read(size_t(compSize), size_t(compSize), + reinterpret_cast(compBuffer.data()))) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read compressedInts."); + } + + bool ret = Compressor::DecompressFromBuffer( + compBuffer.data(), size_t(compSize), out, num_ints, &_err); + + REDUCE_MEMORY_USAGE(compBufferSize); + + return ret; +} + +template +bool CrateReader::ReadIntArray(bool is_compressed, std::vector *d) { + + size_t length{0}; // uncompressed array elements. + // < ver 0.7.0 use 32bit + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t n; + if (!_sr->read4(&n)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the number of array elements."); + } + length = size_t(n); + } else { + uint64_t n; + if (!_sr->read8(&n)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the number of array elements."); + return false; + } + + DCOUT("array.len = " << n); + length = size_t(n); + } + + DCOUT("array.len = " << length); + if (length == 0) { + d->clear(); + return true; + } + + if (length > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Too large array elements."); + } + + CHECK_MEMORY_USAGE(sizeof(T) * length); + + d->resize(length); + + if (!is_compressed) { + + // TODO(syoyo): Zero-copy + if (!_sr->read(sizeof(T) * length, sizeof(T) * length, + reinterpret_cast(d->data()))) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read integer array data."); + } + + return true; + + } else { + + if (length < crate::kMinCompressedArraySize) { + size_t sz = sizeof(T) * length; + // Not stored in compressed for smaller data + if (!_sr->read(sz, sz, reinterpret_cast(d->data()))) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read uncompressed integer array data."); + } + return true; + } + + return ReadCompressedInts(d->data(), d->size()); + } +} + +bool CrateReader::ReadHalfArray(bool is_compressed, + std::vector *d) { + size_t length; + // < ver 0.7.0 use 32bit + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t n; + if (!_sr->read4(&n)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the number of array elements."); + } + length = size_t(n); + } else { + uint64_t n; + if (!_sr->read8(&n)) { + _err += "Failed to read the number of array elements.\n"; + return false; + } + + length = size_t(n); + } + + if (length > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Too many array elements {}.", length)); + } + + CHECK_MEMORY_USAGE(length * sizeof(uint16_t)); + + d->resize(length); + + if (!is_compressed) { + + + // TODO(syoyo): Zero-copy + if (!_sr->read(sizeof(uint16_t) * length, sizeof(uint16_t) * length, + reinterpret_cast(d->data()))) { + _err += "Failed to read half array data.\n"; + return false; + } + + return true; + } else { + + // + // compressed data is represented by integers or look-up table. + // + + if (length < crate::kMinCompressedArraySize) { + size_t sz = sizeof(uint16_t) * length; + // Not stored in compressed. + // reader.ReadContiguous(odata, osize); + if (!_sr->read(sz, sz, reinterpret_cast(d->data()))) { + _err += "Failed to read uncompressed array data.\n"; + return false; + } + return true; + } + + // Read the code + char code; + if (!_sr->read1(&code)) { + _err += "Failed to read the code.\n"; + return false; + } + + if (code == 'i') { + // Compressed integers. + std::vector ints; + ints.resize(length); + if (!ReadCompressedInts(ints.data(), ints.size())) { + _err += "Failed to read compressed ints in ReadHalfArray.\n"; + return false; + } + for (size_t i = 0; i < length; i++) { + float f = float(ints[i]); + value::half h = value::float_to_half_full(f); + (*d)[i] = h; + } + } else if (code == 't') { + // Lookup table & indexes. + uint32_t lutSize; + if (!_sr->read4(&lutSize)) { + _err += "Failed to read lutSize in ReadHalfArray.\n"; + return false; + } + + std::vector lut; + lut.resize(lutSize); + if (!_sr->read(sizeof(value::half) * lutSize, sizeof(value::half) * lutSize, + reinterpret_cast(lut.data()))) { + _err += "Failed to read lut table in ReadHalfArray.\n"; + return false; + } + + std::vector indexes; + indexes.resize(length); + if (!ReadCompressedInts(indexes.data(), indexes.size())) { + _err += "Failed to read lut indices in ReadHalfArray.\n"; + return false; + } + + auto o = d->data(); + for (auto index : indexes) { + *o++ = lut[index]; + } + } else { + _err += "Invalid code. Data is currupted\n"; + return false; + } + + return true; + } + +} + +bool CrateReader::ReadFloatArray(bool is_compressed, std::vector *d) { + + size_t length; + // < ver 0.7.0 use 32bit + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t n; + if (!_sr->read4(&n)) { + _err += "Failed to read the number of array elements.\n"; + return false; + } + length = size_t(n); + } else { + uint64_t n; + if (!_sr->read8(&n)) { + _err += "Failed to read the number of array elements.\n"; + return false; + } + + length = size_t(n); + } + + if (length > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Too many array elements."); + } + + CHECK_MEMORY_USAGE(length * sizeof(float)); + + d->resize(length); + + if (!is_compressed) { + + // TODO(syoyo): Zero-copy + if (!_sr->read(sizeof(float) * length, sizeof(float) * length, + reinterpret_cast(d->data()))) { + _err += "Failed to read float array data.\n"; + return false; + } + + return true; + } else { + + // + // compressed data is represented by integers or look-up table. + // + + if (length < crate::kMinCompressedArraySize) { + size_t sz = sizeof(float) * length; + // Not stored in compressed. + // reader.ReadContiguous(odata, osize); + if (!_sr->read(sz, sz, reinterpret_cast(d->data()))) { + _err += "Failed to read uncompressed array data.\n"; + return false; + } + return true; + } + + // Read the code + char code; + if (!_sr->read1(&code)) { + _err += "Failed to read the code.\n"; + return false; + } + + if (code == 'i') { + // Compressed integers. + std::vector ints; + ints.resize(length); + if (!ReadCompressedInts(ints.data(), ints.size())) { + _err += "Failed to read compressed ints in ReadFloatArray.\n"; + return false; + } + std::copy(ints.begin(), ints.end(), d->data()); + } else if (code == 't') { + // Lookup table & indexes. + uint32_t lutSize; + if (!_sr->read4(&lutSize)) { + _err += "Failed to read lutSize in ReadFloatArray.\n"; + return false; + } + + std::vector lut; + lut.resize(lutSize); + if (!_sr->read(sizeof(float) * lutSize, sizeof(float) * lutSize, + reinterpret_cast(lut.data()))) { + _err += "Failed to read lut table in ReadFloatArray.\n"; + return false; + } + + std::vector indexes; + indexes.resize(length); + if (!ReadCompressedInts(indexes.data(), indexes.size())) { + _err += "Failed to read lut indices in ReadFloatArray.\n"; + return false; + } + + auto o = d->data(); + for (auto index : indexes) { + *o++ = lut[index]; + } + } else { + _err += "Invalid code. Data is currupted\n"; + return false; + } + + return true; + } + +} + +bool CrateReader::ReadDoubleArray(bool is_compressed, std::vector *d) { + + size_t length; + // < ver 0.7.0 use 32bit + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t n; + if (!_sr->read4(&n)) { + _err += "Failed to read the number of array elements.\n"; + return false; + } + length = size_t(n); + } else { + uint64_t n; + if (!_sr->read8(&n)) { + _err += "Failed to read the number of array elements.\n"; + return false; + } + + length = size_t(n); + } + + if (length > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Too many array elements."); + } + + CHECK_MEMORY_USAGE(length * sizeof(double)); + + d->resize(length); + + if (!is_compressed) { + + // TODO(syoyo): Zero-copy + if (!_sr->read(sizeof(double) * length, sizeof(double) * length, + reinterpret_cast(d->data()))) { + _err += "Failed to read double array data.\n"; + return false; + } + + return true; + } else { + + // + // compressed data is represented by integers or look-up table. + // + + d->resize(length); + + if (length < crate::kMinCompressedArraySize) { + size_t sz = sizeof(double) * length; + // Not stored in compressed. + // reader.ReadContiguous(odata, osize); + if (!_sr->read(sz, sz, reinterpret_cast(d->data()))) { + _err += "Failed to read uncompressed array data.\n"; + return false; + } + return true; + } + + // Read the code + char code; + if (!_sr->read1(&code)) { + _err += "Failed to read the code.\n"; + return false; + } + + if (code == 'i') { + // Compressed integers. + std::vector ints; + ints.resize(length); + if (!ReadCompressedInts(ints.data(), ints.size())) { + _err += "Failed to read compressed ints in ReadDoubleArray.\n"; + return false; + } + std::copy(ints.begin(), ints.end(), d->data()); + } else if (code == 't') { + // Lookup table & indexes. + uint32_t lutSize; + if (!_sr->read4(&lutSize)) { + _err += "Failed to read lutSize in ReadDoubleArray.\n"; + return false; + } + + std::vector lut; + lut.resize(lutSize); + if (!_sr->read(sizeof(double) * lutSize, sizeof(double) * lutSize, + reinterpret_cast(lut.data()))) { + _err += "Failed to read lut table in ReadDoubleArray.\n"; + return false; + } + + std::vector indexes; + indexes.resize(length); + if (!ReadCompressedInts(indexes.data(), indexes.size())) { + _err += "Failed to read lut indices in ReadDoubleArray.\n"; + return false; + } + + auto o = d->data(); + for (auto index : indexes) { + *o++ = lut[index]; + } + } else { + _err += "Invalid code. Data is currupted\n"; + return false; + } + + return true; + } +} + +bool CrateReader::ReadTimeSamples(value::TimeSamples *d) { + + // Layout + // + // - `times`(double[]) + // - NumValueReps(int64) + // - ArrayOfValueRep + // + + // TODO(syoyo): Deferred loading of TimeSamples?(See USD's implementation for details) + + DCOUT("ReadTimeSamples: offt before tell = " << _sr->tell()); + + // 8byte for the offset for recursive value. See RecursiveRead() in + // https://github.com/PixarAnimationStudios/USD/blob/release/pxr/usd/usd/crateFile.cpp for details. + int64_t offset{0}; + if (!_sr->read8(&offset)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the offset for value in Dictionary."); + return false; + } + + DCOUT("TimeSample times value offset = " << offset); + DCOUT("TimeSample tell = " << _sr->tell()); + + // -8 to compensate sizeof(offset) + if (!_sr->seek_from_current(offset - 8)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to seek to TimeSample times. Invalid offset value: " + + std::to_string(offset)); + } + + // TODO(syoyo): Deduplicate times? + + crate::ValueRep times_rep{0}; + if (!ReadValueRep(×_rep)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read ValueRep for TimeSample' `times` element."); + } + + // Save offset + auto values_offset = _sr->tell(); + + // TODO: Enable Check if type `double[]` +#if 0 + if (times_rep.GetType() == crate::CrateDataTypeId::CRATE_DATA_TYPE_DOUBLE_VECTOR) { + // ok + } else if ((times_rep.GetType() == crate::CrateDataTypeId::CRATE_DATA_TYPE_DOUBOLE) && times_rep.IsArray()) { + // ok + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("`times` value must be type `double[]`, but got type `{}`", times_rep.GetTypeName())); + } +#endif + + crate::CrateValue times_value; + if (!UnpackValueRep(times_rep, ×_value)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to unpack value of TimeSample's `times` element."); + } + + // must be an array of double. + DCOUT("TimeSample times:" << times_value.type_name()); + + std::vector times; + if (auto pv = times_value.get_value>()) { + times = pv.value(); + DCOUT("`times` = " << times); + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("`times` in TimeSamples must be type `double[]`, but got type `{}`", times_value.type_name())); + } + + // + // Parse values(elements) of TimeSamples. + // + + // seek position will be changed in `_UnpackValueRep`, so revert it. + if (!_sr->seek_set(values_offset)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to seek to TimeSamples values."); + } + + // 8byte for the offset for recursive value. See RecursiveRead() in + // crateFile.cpp for details. + if (!_sr->read8(&offset)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the offset for value in TimeSamples."); + return false; + } + + DCOUT("TimeSample value offset = " << offset); + DCOUT("TimeSample tell = " << _sr->tell()); + + // -8 to compensate sizeof(offset) + if (!_sr->seek_from_current(offset - 8)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to seek to TimeSample values. Invalid offset value: " + std::to_string(offset)); + } + + uint64_t num_values{0}; + if (!_sr->read8(&num_values)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the number of values from TimeSamples."); + return false; + } + + DCOUT("Number of values = " << num_values); + + if (times.size() != num_values) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "# of `times` elements and # of values in Crate differs."); + } + + for (size_t i = 0; i < num_values; i++) { + + crate::ValueRep rep; + if (!ReadValueRep(&rep)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read ValueRep for TimeSample' value element."); + } + + auto next_vrep_loc = _sr->tell(); + + /// + /// Type check of the content of `value` will be done at ReconstructPrim() in usdc-reader.cc. + /// + crate::CrateValue value; + if (!UnpackValueRep(rep, &value)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to unpack value of TimeSample's value element."); + } + + d->add_sample(times[i], value.get_raw()); + + // UnpackValueRep() will change StreamReader's read position. + // Revert to next ValueRep location here. + _sr->seek_set(next_vrep_loc); + } + + // Move to next location. + // sizeof(uint64) = sizeof(ValueRep) + _sr->seek_set(values_offset); + if (!_sr->seek_from_current(int64_t(sizeof(uint64_t) * num_values))) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to seek over TimeSamples's values."); + } + + + return true; +} + +bool CrateReader::ReadStringArray(std::vector *d) { + // array data is not compressed + auto ReadFn = [this](std::vector &result) -> bool { + uint64_t n{0}; + if (!_sr->read8(&n)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the number of array elements."); + return false; + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Too many array elements."); + } + + CHECK_MEMORY_USAGE(size_t(n) * sizeof(crate::Index)); + + std::vector ivalue(static_cast(n)); + + if (!_sr->read(size_t(n) * sizeof(crate::Index), + size_t(n) * sizeof(crate::Index), + reinterpret_cast(ivalue.data()))) { + PUSH_ERROR("Failed to read STRING_VECTOR data."); + return false; + } + + // reconstruct + CHECK_MEMORY_USAGE(size_t(n) * sizeof(void *)); + result.resize(static_cast(n)); + for (size_t i = 0; i < n; i++) { + if (auto v = GetStringToken(ivalue[i])) { + std::string s = v.value().str(); + CHECK_MEMORY_USAGE(s.size()); + result[i] = s; + } else { + PUSH_ERROR("Invalid StringIndex."); + } + } + + return true; + }; + + std::vector items; + if (!ReadFn(items)) { + return false; + } + + (*d) = items; + + return true; +} + +bool CrateReader::ReadReference(Reference *d) { + + if (!d) { + return false; + } + + // assetPath : string + // primPath : Path + // layerOffset : LayerOffset + // customData : Dict + + std::string assetPath; + if (!ReadString(&assetPath)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read assetPath in Reference ValueRep."); + } + + crate::PathIndex index; + if (!ReadIndex(&index)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read primPath Index in Reference ValueRep."); + } + + auto path = GetPath(index); + if (!path) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid Path index in Reference ValueRep."); + } + + LayerOffset layerOffset; + if (!ReadLayerOffset(&layerOffset)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read LayerOffset in Reference ValueRep."); + } + + CustomDataType customData; + if (!ReadCustomData(&customData)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read CustomData(Dict) in Reference ValueRep."); + } + + d->asset_path = assetPath; + d->prim_path = path.value(); + d->layerOffset = layerOffset; + d->customData = customData; + + return true; +} + +bool CrateReader::ReadPayload(Payload *d) { + + if (!d) { + return false; + } + + // assetPath : string + // primPath : Path + + std::string assetPath; + if (!ReadString(&assetPath)) { + return false; + } + + + crate::PathIndex index; + if (!ReadIndex(&index)) { + return false; + } + + auto path = GetPath(index); + if (!path) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid Path index in Payload ValueRep."); + } + + // LayerOffset from 0.8.0 + if (VersionGreaterThanOrEqualTo_0_8_0()) { + LayerOffset layerOffset; + if (!ReadLayerOffset(&layerOffset)) { + return false; + } + d->layerOffset = layerOffset; + } + + d->asset_path = assetPath; + d->prim_path = path.value(); + + return true; +} + +bool CrateReader::ReadLayerOffset(LayerOffset *d) { + static_assert(sizeof(LayerOffset) == 8 * 2, "LayerOffset must be 16bytes"); + + // double x 2 + if (!_sr->read(sizeof(double), sizeof(double), reinterpret_cast(&(d->_offset)))) { + return false; + } + if (!_sr->read(sizeof(double), sizeof(double), reinterpret_cast(&(d->_scale)))) { + return false; + } + + return true; +} + +bool CrateReader::ReadLayerOffsetArray(std::vector *d) { + // array data is not compressed + + uint64_t n{0}; + if (!_sr->read8(&n)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the number of array elements."); + return false; + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Too many array elements."); + } + + if (n == 0) { + return true; + } + + CHECK_MEMORY_USAGE(size_t(n) * sizeof(LayerOffset)); + + d->resize(size_t(n)); + + if (!_sr->read(size_t(n) * sizeof(LayerOffset), + size_t(n) * sizeof(LayerOffset), + reinterpret_cast(d->data()))) { + PUSH_ERROR("Failed to read LayerOffset[] data."); + return false; + } + + return true; +} + +bool CrateReader::ReadPathArray(std::vector *d) { + // array data is not compressed + auto ReadFn = [this](std::vector &result) -> bool { + uint64_t n{0}; + if (!_sr->read8(&n)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the number of array elements."); + return false; + } + + if (n > _config.maxArrayElements) { + _err += "Too many Path array elements.\n"; + return false; + } + + CHECK_MEMORY_USAGE(size_t(n) * sizeof(crate::Index)); + + std::vector ivalue(static_cast(n)); + + if (!_sr->read(size_t(n) * sizeof(crate::Index), + size_t(n) * sizeof(crate::Index), + reinterpret_cast(ivalue.data()))) { + _err += "Failed to read ListOp data.\n"; + return false; + } + + // reconstruct + result.resize(static_cast(n)); + for (size_t i = 0; i < n; i++) { + if (auto pv = GetPath(ivalue[i])) { + result[i] = pv.value(); + } else { + PUSH_ERROR("Invalid Index for Path."); + return false; + } + } + + return true; + }; + + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read Path vector.\n"; + return false; + } + + (*d) = items; + + return true; +} + +bool CrateReader::ReadTokenListOp(ListOp *d) { + // read ListOpHeader + ListOpHeader h; + if (!_sr->read1(&h.bits)) { + _err += "Failed to read ListOpHeader\n"; + return false; + } + + if (h.IsExplicit()) { + d->ClearAndMakeExplicit(); + } + + // array data is not compressed + auto ReadFn = [this](std::vector &result) -> bool { + uint64_t n; + if (!_sr->read8(&n)) { + _err += "Failed to read # of elements in ListOp.\n"; + return false; + } + + if (n > _config.maxArrayElements) { + _err += "Too many ListOp elements.\n"; + return false; + } + + CHECK_MEMORY_USAGE(size_t(n) * sizeof(crate::Index)); + + std::vector ivalue(static_cast(n)); + + if (!_sr->read(size_t(n) * sizeof(crate::Index), + size_t(n) * sizeof(crate::Index), + reinterpret_cast(ivalue.data()))) { + _err += "Failed to read ListOp data.\n"; + return false; + } + + // reconstruct + result.resize(static_cast(n)); + for (size_t i = 0; i < n; i++) { + if (auto v = GetToken(ivalue[i])) { + result[i] = v.value(); + } else { + return false; + } + } + + return true; + }; + + if (h.HasExplicitItems()) { + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read ListOp::ExplicitItems.\n"; + return false; + } + + d->SetExplicitItems(items); + } + + if (h.HasAddedItems()) { + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read ListOp::AddedItems.\n"; + return false; + } + + d->SetAddedItems(items); + } + + if (h.HasPrependedItems()) { + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read ListOp::PrependedItems.\n"; + return false; + } + + d->SetPrependedItems(items); + } + + if (h.HasAppendedItems()) { + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read ListOp::AppendedItems.\n"; + return false; + } + + d->SetAppendedItems(items); + } + + if (h.HasDeletedItems()) { + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read ListOp::DeletedItems.\n"; + return false; + } + + d->SetDeletedItems(items); + } + + if (h.HasOrderedItems()) { + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read ListOp::OrderedItems.\n"; + return false; + } + + d->SetOrderedItems(items); + } + + return true; +} + +bool CrateReader::ReadStringListOp(ListOp *d) { + // read ListOpHeader + ListOpHeader h; + if (!_sr->read1(&h.bits)) { + _err += "Failed to read ListOpHeader\n"; + return false; + } + + if (h.IsExplicit()) { + d->ClearAndMakeExplicit(); + } + + // array data is not compressed + auto ReadFn = [this](std::vector &result) -> bool { + uint64_t n{0}; + if (!_sr->read8(&n)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the number of array elements."); + return false; + } + + + if (n > _config.maxArrayElements) { + _err += "Too many ListOp elements.\n"; + return false; + } + + CHECK_MEMORY_USAGE(size_t(n) * sizeof(crate::Index)); + + std::vector ivalue(static_cast(n)); + + if (!_sr->read(size_t(n) * sizeof(crate::Index), + size_t(n) * sizeof(crate::Index), + reinterpret_cast(ivalue.data()))) { + _err += "Failed to read ListOp data.\n"; + return false; + } + + // reconstruct + result.resize(static_cast(n)); + for (size_t i = 0; i < n; i++) { + if (auto v = GetStringToken(ivalue[i])) { + result[i] = v.value().str(); + } else { + return false; + } + } + + return true; + }; + + if (h.HasExplicitItems()) { + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read ListOp::ExplicitItems.\n"; + return false; + } + + d->SetExplicitItems(items); + } + + if (h.HasAddedItems()) { + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read ListOp::AddedItems.\n"; + return false; + } + + d->SetAddedItems(items); + } + + if (h.HasPrependedItems()) { + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read ListOp::PrependedItems.\n"; + return false; + } + + d->SetPrependedItems(items); + } + + if (h.HasAppendedItems()) { + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read ListOp::AppendedItems.\n"; + return false; + } + + d->SetAppendedItems(items); + } + + if (h.HasDeletedItems()) { + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read ListOp::DeletedItems.\n"; + return false; + } + + d->SetDeletedItems(items); + } + + if (h.HasOrderedItems()) { + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read ListOp::OrderedItems.\n"; + return false; + } + + d->SetOrderedItems(items); + } + + return true; +} + +bool CrateReader::ReadPathListOp(ListOp *d) { + // read ListOpHeader + ListOpHeader h; + if (!_sr->read1(&h.bits)) { + PUSH_ERROR("Failed to read ListOpHeader."); + return false; + } + + if (h.IsExplicit()) { + DCOUT("IsExplicit()"); + d->ClearAndMakeExplicit(); + } + + // array data is not compressed + auto ReadFn = [this](std::vector &result) -> bool { + uint64_t n{0}; + if (!_sr->read8(&n)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the number of array elements."); + return false; + } + + if (n > _config.maxArrayElements) { + _err += "Too many ListOp elements.\n"; + return false; + } + + CHECK_MEMORY_USAGE(size_t(n) * sizeof(crate::Index)); + + std::vector ivalue(static_cast(n)); + + if (!_sr->read(size_t(n) * sizeof(crate::Index), + size_t(n) * sizeof(crate::Index), + reinterpret_cast(ivalue.data()))) { + PUSH_ERROR("Failed to read ListOp data.."); + return false; + } + + // reconstruct + result.resize(static_cast(n)); + for (size_t i = 0; i < n; i++) { + if (auto pv = GetPath(ivalue[i])) { + result[i] = pv.value(); + } else { + PUSH_ERROR("Invalid Index for Path."); + return false; + } + } + + return true; + }; + + if (h.HasExplicitItems()) { + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read ListOp::ExplicitItems.\n"; + return false; + } + + d->SetExplicitItems(items); + } + + if (h.HasAddedItems()) { + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read ListOp::AddedItems.\n"; + return false; + } + + d->SetAddedItems(items); + } + + if (h.HasPrependedItems()) { + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read ListOp::PrependedItems.\n"; + return false; + } + + d->SetPrependedItems(items); + } + + if (h.HasAppendedItems()) { + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read ListOp::AppendedItems.\n"; + return false; + } + + d->SetAppendedItems(items); + } + + if (h.HasDeletedItems()) { + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read ListOp::DeletedItems.\n"; + return false; + } + + d->SetDeletedItems(items); + } + + if (h.HasOrderedItems()) { + std::vector items; + if (!ReadFn(items)) { + _err += "Failed to read ListOp::OrderedItems.\n"; + return false; + } + + d->SetOrderedItems(items); + } + + return true; +} + +template<> +bool CrateReader::ReadArray(std::vector *d) { + + if (!d) { + return false; + } + + uint64_t n{0}; + if (!_sr->read8(&n)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the number of array elements."); + return false; + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Too many array elements."); + } + + CHECK_MEMORY_USAGE(sizeof(Reference) * n); + + for (size_t i = 0; i < n; i++) { + Reference p; + if (!ReadReference(&p)) { + return false; + } + d->emplace_back(p); + } + + return true; +} + +template<> +bool CrateReader::ReadArray(std::vector *d) { + + if (!d) { + return false; + } + + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the number of array elements."); + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the number of array elements."); + return false; + } + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Too many array elements."); + } + + CHECK_MEMORY_USAGE(sizeof(Payload) * n); + + for (size_t i = 0; i < n; i++) { + Payload p; + if (!ReadPayload(&p)) { + return false; + } + d->emplace_back(p); + } + + return true; +} + +// T = int, uint, int64, uint64 +template +//typename std::enable_if::value, bool>::type +bool CrateReader::ReadArray(std::vector *d) { + + if (!d) { + return false; + } + + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the number of array elements."); + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the number of array elements."); + return false; + } + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Too many array elements."); + } + + if (n == 0) { + return true; + } + + CHECK_MEMORY_USAGE(sizeof(T) * size_t(n)); + + d->resize(size_t(n)); + if (_sr->read(sizeof(T) * n, sizeof(T) * size_t(n), reinterpret_cast(d->data()))) { + return false; + } + + return true; +} + +template +bool CrateReader::ReadListOp(ListOp *d) { + // read ListOpHeader + ListOpHeader h; + if (!_sr->read1(&h.bits)) { + PUSH_ERROR("Failed to read ListOpHeader."); + return false; + } + + if (h.IsExplicit()) { + d->ClearAndMakeExplicit(); + } + + // + // NOTE: array data is not compressed even for Int type + // + + if (h.HasExplicitItems()) { + std::vector items; + if (!ReadArray(&items)) { + _err += "Failed to read ListOp::ExplicitItems.\n"; + return false; + } + + d->SetExplicitItems(items); + } + + if (h.HasAddedItems()) { + std::vector items; + if (!ReadArray(&items)) { + _err += "Failed to read ListOp::AddedItems.\n"; + return false; + } + + d->SetAddedItems(items); + } + + if (h.HasPrependedItems()) { + std::vector items; + if (!ReadArray(&items)) { + _err += "Failed to read ListOp::PrependedItems.\n"; + return false; + } + + d->SetPrependedItems(items); + } + + if (h.HasAppendedItems()) { + std::vector items; + if (!ReadArray(&items)) { + _err += "Failed to read ListOp::AppendedItems.\n"; + return false; + } + + d->SetAppendedItems(items); + } + + if (h.HasDeletedItems()) { + std::vector items; + if (!ReadArray(&items)) { + _err += "Failed to read ListOp::DeletedItems.\n"; + return false; + } + + d->SetDeletedItems(items); + } + + if (h.HasOrderedItems()) { + std::vector items; + if (!ReadArray(&items)) { + _err += "Failed to read ListOp::OrderedItems.\n"; + return false; + } + + d->SetOrderedItems(items); + } + + return true; +} + + +bool CrateReader::ReadVariantSelectionMap(VariantSelectionMap *d) { + + if (!d) { + return false; + } + + // map + + // n + // [key, value] * n + + uint64_t sz; + if (!_sr->read8(&sz)) { + _err += "Failed to read the number of elements for VariantsMap data.\n"; + return false; + } + + if (sz > _config.maxVariantsMapElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "The number of elements for VariantsMap data is too large. Max = " << std::to_string(_config.maxVariantsMapElements) << ", but got " << std::to_string(sz)); + } + + for (size_t i = 0; i < sz; i++) { + std::string key; + if (!ReadString(&key)) { + return false; + } + + std::string value; + if (!ReadString(&value)) { + return false; + } + + // TODO: Duplicate key check? + d->emplace(key, value); + } + + return true; +} + +bool CrateReader::ReadCustomData(CustomDataType *d) { + CustomDataType dict; + uint64_t sz; + if (!_sr->read8(&sz)) { + _err += "Failed to read the number of elements for Dictionary data.\n"; + return false; + } + + if (sz > _config.maxDictElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "The number of elements for Dictionary data is too large. Max = " << std::to_string(_config.maxDictElements) << ", but got " << std::to_string(sz)); + } + + DCOUT("# o elements in dict" << sz); + + while (sz--) { + // key(StringIndex) + std::string key; + + if (!ReadString(&key)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read key string for Dictionary element."); + } + + // 8byte for the offset for recursive value. See RecursiveRead() in + // crateFile.cpp for details. + int64_t offset{0}; + if (!_sr->read8(&offset)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the offset for value in Dictionary."); + } + + // -8 to compensate sizeof(offset) + if (!_sr->seek_from_current(offset - 8)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to seek. Invalid offset value: " + std::to_string(offset)); + } + + DCOUT("key = " << key); + + crate::ValueRep rep{0}; + if (!ReadValueRep(&rep)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read value for Dictionary element."); + } + + DCOUT("vrep =" << crate::GetCrateDataTypeName(rep.GetType())); + + auto saved_position = _sr->tell(); + + crate::CrateValue value; + if (!UnpackValueRep(rep, &value)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to unpack value of Dictionary element."); + } + + if (dict.count(key)) { + // Duplicated key. maybe ok? + } + // CrateValue -> MetaVariable + MetaVariable var; + + var.set_value(key, value.get_raw()); + + dict[key] = var; + + if (!_sr->seek_set(saved_position)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to set seek."); + } + } + + (*d) = std::move(dict); + return true; +} + +bool CrateReader::UnpackInlinedValueRep(const crate::ValueRep &rep, + crate::CrateValue *value) { + if (!rep.IsInlined()) { + PUSH_ERROR("ValueRep must be inlined value representation."); + return false; + } + + const auto tyRet = crate::GetCrateDataType(rep.GetType()); + if (!tyRet) { + PUSH_ERROR(tyRet.error()); + return false; + } + + if (rep.IsCompressed()) { + PUSH_ERROR("Inlinved value must not be compressed."); + return false; + } + + if (rep.IsArray()) { + PUSH_ERROR("Inlined value must not be an array."); + return false; + } + + const auto dty = tyRet.value(); + DCOUT(crate::GetCrateDataTypeRepr(dty)); + + uint32_t d = (rep.GetPayload() & ((1ull << (sizeof(uint32_t) * 8)) - 1)); + DCOUT("d = " << d); + + // TODO(syoyo): Use template SFINE? + switch (dty.dtype_id) { + case crate::CrateDataTypeId::NumDataTypes: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_INVALID: { + PUSH_ERROR("`Invalid` DataType."); + return false; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_BOOL: { + value->Set(d ? true : false); + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_ASSET_PATH: { + // AssetPath = TokenIndex for inlined value. + if (auto v = GetToken(crate::Index(d))) { + std::string str = v.value().str(); + + value::AssetPath assetp(str); + value->Set(assetp); + return true; + } else { + PUSH_ERROR("Invalid Index for AssetPath."); + return false; + } + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_TOKEN: { + if (auto v = GetToken(crate::Index(d))) { + value::token tok = v.value(); + + DCOUT("value.token = " << tok); + + value->Set(tok); + + return true; + } else { + PUSH_ERROR("Invalid Index for Token."); + return false; + } + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_STRING: { + if (auto v = GetStringToken(crate::Index(d))) { + std::string str = v.value().str(); + + DCOUT("value.string = " << str); + + value->Set(str); + + return true; + } else { + PUSH_ERROR("Invalid Index for StringToken."); + return false; + } + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_SPECIFIER: { + if (d >= static_cast(Specifier::Invalid)) { + _err += "Invalid value for Specifier\n"; + return false; + } + Specifier val = static_cast(d); + + value->Set(val); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_PERMISSION: { + if (d >= static_cast(Permission::Invalid)) { + _err += "Invalid value for Permission\n"; + return false; + } + Permission val = static_cast(d); + + value->Set(val); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VARIABILITY: { + if (d >= static_cast(Variability::Invalid)) { + _err += "Invalid value for Variability\n"; + return false; + } + Variability val = static_cast(d); + + value->Set(val); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_UCHAR: { + uint8_t val; + memcpy(&val, &d, 1); + + DCOUT("value.uchar = " << val); + + value->Set(val); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_INT: { + int ival; + memcpy(&ival, &d, sizeof(int)); + + DCOUT("value.int = " << ival); + + value->Set(ival); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_UINT: { + uint32_t val; + memcpy(&val, &d, sizeof(uint32_t)); + + DCOUT("value.uint = " << val); + + value->Set(val); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_INT64: { + // stored as int + int _ival; + memcpy(&_ival, &d, sizeof(int)); + + DCOUT("value.int = " << _ival); + + int64_t ival = static_cast(_ival); + + value->Set(ival); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_UINT64: { + // stored as uint32 + uint32_t _ival; + memcpy(&_ival, &d, sizeof(uint32_t)); + + DCOUT("value.int = " << _ival); + + uint64_t ival = static_cast(_ival); + + value->Set(ival); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_HALF: { + value::half f; + memcpy(&f, &d, sizeof(value::half)); + + DCOUT("value.half = " << f); + + value->Set(f); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_FLOAT: { + float f; + memcpy(&f, &d, sizeof(float)); + + DCOUT("value.float = " << f); + + value->Set(f); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_DOUBLE: { + // stored as float + float _f; + memcpy(&_f, &d, sizeof(float)); + + double f = static_cast(_f); + + value->Set(f); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_MATRIX2D: { + // Matrix contains diagnonal components only, and values are represented + // in int8 + int8_t data[2]; + memcpy(&data, &d, 2); + + value::matrix2d v; + memset(v.m, 0, sizeof(value::matrix2d)); + v.m[0][0] = static_cast(data[0]); + v.m[1][1] = static_cast(data[1]); + + DCOUT("value.matrix(diag) = " << v.m[0][0] << ", " << v.m[1][1] << "\n"); + + value->Set(v); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_MATRIX3D: { + // Matrix contains diagnonal components only, and values are represented + // in int8 + int8_t data[3]; + memcpy(&data, &d, 3); + + value::matrix3d v; + memset(v.m, 0, sizeof(value::matrix3d)); + v.m[0][0] = static_cast(data[0]); + v.m[1][1] = static_cast(data[1]); + v.m[2][2] = static_cast(data[2]); + + DCOUT("value.matrix(diag) = " << v.m[0][0] << ", " << v.m[1][1] << ", " + << v.m[2][2] << "\n"); + + value->Set(v); + + return true; + } + + case crate::CrateDataTypeId::CRATE_DATA_TYPE_MATRIX4D: { + // Matrix contains diagnonal components only, and values are represented + // in int8 + int8_t data[4]; + memcpy(&data, &d, 4); + + value::matrix4d v; + memset(v.m, 0, sizeof(value::matrix4d)); + v.m[0][0] = static_cast(data[0]); + v.m[1][1] = static_cast(data[1]); + v.m[2][2] = static_cast(data[2]); + v.m[3][3] = static_cast(data[3]); + + DCOUT("value.matrix(diag) = " << v.m[0][0] << ", " << v.m[1][1] << ", " + << v.m[2][2] << ", " << v.m[3][3]); + + value->Set(v); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_QUATD: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_QUATF: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_QUATH: { + // Seems quaternion type is not allowed for Inlined Value. + PUSH_ERROR("Quaternion type is not allowed for Inlined Value."); + return false; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC2D: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC2F: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC2H: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC2I: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC3D: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC3F: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC3H: { + // Value is represented in int8 + int8_t data[3]; + memcpy(&data, &d, 3); + + value::half3 v; + v[0] = value::float_to_half_full(float(data[0])); + v[1] = value::float_to_half_full(float(data[1])); + v[2] = value::float_to_half_full(float(data[2])); + + DCOUT("value.half3 = " << v); + + value->Set(v); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC3I: { + // Value is represented in int8 + int8_t data[3]; + memcpy(&data, &d, 3); + + value::int3 v; + v[0] = static_cast(data[0]); + v[1] = static_cast(data[1]); + v[2] = static_cast(data[2]); + + DCOUT("value.int3 = " << v); + + value->Set(v); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC4D: { + // Value is represented in int8 + int8_t data[4]; + memcpy(&data, &d, 4); + + value::double4 v; + v[0] = static_cast(data[0]); + v[1] = static_cast(data[1]); + v[2] = static_cast(data[2]); + v[3] = static_cast(data[3]); + + DCOUT("value.doublef = " << v); + + value->Set(v); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC4F: { + // Value is represented in int8 + int8_t data[4]; + memcpy(&data, &d, 4); + + value::float4 v; + v[0] = static_cast(data[0]); + v[1] = static_cast(data[1]); + v[2] = static_cast(data[2]); + v[3] = static_cast(data[3]); + + DCOUT("value.vec4f = " << v); + + value->Set(v); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC4H: { + // Value is represented in int8 + int8_t data[4]; + memcpy(&data, &d, 4); + + value::half4 v; + v[0] = value::float_to_half_full(float(data[0])); + v[1] = value::float_to_half_full(float(data[0])); + v[2] = value::float_to_half_full(float(data[0])); + v[3] = value::float_to_half_full(float(data[0])); + + DCOUT("value.vec4h = " << v); + + value->Set(v); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC4I: { + // Value is represented in int8 + int8_t data[4]; + memcpy(&data, &d, 4); + + value::int4 v; + v[0] = static_cast(data[0]); + v[1] = static_cast(data[1]); + v[2] = static_cast(data[2]); + v[3] = static_cast(data[3]); + + DCOUT("value.vec4i = " << v); + + value->Set(v); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_DICTIONARY: { + // empty dict is allowed + // TODO: empty(zero value) check? + //crate::CrateValue::Dictionary dict; + CustomDataType dict; // use CustomDataType for Dict + value->Set(dict); + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VALUE_BLOCK: { + // Guess No content for ValueBlock + value::ValueBlock block; + value->Set(block); + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_TOKEN_LIST_OP: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_STRING_LIST_OP: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_PATH_LIST_OP: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_REFERENCE_LIST_OP: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_INT_LIST_OP: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_INT64_LIST_OP: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_UINT_LIST_OP: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_UINT64_LIST_OP: { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("ListOp data type `{}` cannot be inlined.", + crate::GetCrateDataTypeName(dty.dtype_id))); + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_PATH_VECTOR: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_TOKEN_VECTOR: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VARIANT_SELECTION_MAP: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_TIME_SAMPLES: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_DOUBLE_VECTOR: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_PAYLOAD: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_PAYLOAD_LIST_OP: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_LAYER_OFFSET_VECTOR: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_STRING_VECTOR: { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Data type `{}` cannot be inlined.", + crate::GetCrateDataTypeName(dty.dtype_id))); + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VALUE: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_UNREGISTERED_VALUE: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_UNREGISTERED_VALUE_LIST_OP: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_TIME_CODE: { + PUSH_ERROR( + "Invalid data type(or maybe not supported in TinyUSDZ yet) for " + "Inlined value: " + + crate::GetCrateDataTypeName(dty.dtype_id)); + return false; + } + } + + // Should never reach here. + return false; +} + +#if 0 +template +CrateReader::UnpackArrayValue(CrateDataTypeId dty, crate::CrateValue *value_out) { + uint64_t n; + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + + std::vector v(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(crate::Index), + size_t(n) * sizeof(crate::Index), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read array data."); + return false; + } + + return true; +} +#endif + +bool CrateReader::UnpackValueRep(const crate::ValueRep &rep, + crate::CrateValue *value) { + if (rep.IsInlined()) { + return UnpackInlinedValueRep(rep, value); + } + + DCOUT("ValueRep type value = " << rep.GetType()); + auto tyRet = crate::GetCrateDataType(rep.GetType()); + if (!tyRet) { + PUSH_ERROR(tyRet.error()); + } + + const auto dty = tyRet.value(); + +#define TODO_IMPLEMENT(__dty) \ + { \ + PUSH_ERROR("TODO: '" + crate::GetCrateDataTypeName(__dty.dtype_id) + \ + "' data is not yet implemented."); \ + return false; \ + } + +#define COMPRESS_UNSUPPORTED_CHECK(__dty) \ + if (rep.IsCompressed()) { \ + PUSH_ERROR("Compressed [" + crate::GetCrateDataTypeName(__dty.dtype_id) + \ + "' data is not yet supported."); \ + return false; \ + } + +#define NON_ARRAY_UNSUPPORTED_CHECK(__dty) \ + if (!rep.IsArray()) { \ + PUSH_ERROR("Non array '" + crate::GetCrateDataTypeName(__dty.dtype_id) + \ + "' data is not yet supported."); \ + return false; \ + } + +#define ARRAY_UNSUPPORTED_CHECK(__dty) \ + if (rep.IsArray()) { \ + PUSH_ERROR("Array of '" + crate::GetCrateDataTypeName(__dty.dtype_id) + \ + "' data type is not yet supported."); \ + return false; \ + } + + // payload is the offset to data. + uint64_t offset = rep.GetPayload(); + if (!_sr->seek_set(offset)) { + PUSH_ERROR("Invalid offset."); + return false; + } + + switch (dty.dtype_id) { + case crate::CrateDataTypeId::NumDataTypes: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_INVALID: { + DCOUT("dtype_id = " << to_string(uint32_t(dty.dtype_id))); + PUSH_ERROR("`Invalid` DataType."); + return false; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_BOOL: { + COMPRESS_UNSUPPORTED_CHECK(dty) + NON_ARRAY_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + std::vector v; + + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + + // bool is encoded as 8bit value. + + uint64_t n; + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("# of bool array too large. TinyUSDZ limites it up to {}", _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(uint8_t)); + + std::vector data(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(uint8_t), + size_t(n) * sizeof(uint8_t), + reinterpret_cast(data.data()))) { + PUSH_ERROR("Failed to read bool array."); + return false; + } + + // to std::vector, whose underlying storage may use 1bit. + v.resize(size_t(n)); + for (size_t i = 0; i < n; i++) { + v[i] = data[i] ? true : false; + } + + value->Set(v); + return true; + + } else { + // non array bool should be inline encoded. + PUSH_ERROR_AND_RETURN_TAG(kTag, "bool value must be inlined."); + } + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_ASSET_PATH: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + // AssetPath is encoded as StringIndex for uninlined and array value + // NOTE: inlined value uses TokenIndex. + + if (rep.IsArray()) { + + if (rep.GetPayload() == 0) { // empty array + value->Set(std::vector()); + return true; + } + + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the number of array elements."); + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the number of array elements."); + return false; + } + } + + if (n > _config.maxAssetPathElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("# of AssetPaths too large. TinyUSDZ limites it up to {}", _config.maxAssetPathElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(crate::Index)); + + std::vector v(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(crate::Index), + size_t(n) * sizeof(crate::Index), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read StringIndex array."); + return false; + } + + std::vector apaths(static_cast(n)); + + for (size_t i = 0; i < n; i++) { + if (auto tokv = GetStringToken(v[i])) { + DCOUT("StringToken[" << i << "] = " << tokv.value()); + apaths[i] = value::AssetPath(tokv.value().str()); + } else { + return false; + } + } + + value->Set(apaths); + return true; + } else { + + CHECK_MEMORY_USAGE(sizeof(crate::Index)); + + crate::Index v; + if (!_sr->read(sizeof(crate::Index), sizeof(crate::Index), + reinterpret_cast(&v))) { + PUSH_ERROR("Failed to read uint64 data."); + return false; + } + + DCOUT("StrIndex = " << v); + + if (auto tokv = GetStringToken(v)) { + DCOUT("StringToken = " << tokv.value()); + value::AssetPath apath(tokv.value().str()); + value->Set(apath); + } else { + PUSH_ERROR_AND_RETURN("Invalid StringToken found."); + return false; + } + + return true; + } + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_TOKEN: { + COMPRESS_UNSUPPORTED_CHECK(dty) + NON_ARRAY_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + + if (rep.GetPayload() == 0) { // empty array + value->Set(std::vector()); + return true; + } + + uint64_t n; + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Token array too large. TinyUSDZ limits it up to {}", _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(crate::Index)); + + std::vector v; + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(crate::Index), + size_t(n) * sizeof(crate::Index), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read TokenIndex array."); + return false; + } + + std::vector tokens(static_cast(n)); + + for (size_t i = 0; i < n; i++) { + if (auto tokv = GetToken(v[i])) { + DCOUT("Token[" << i << "] = " << tokv.value()); + tokens[i] = tokv.value(); + } else { + return false; + } + } + + value->Set(tokens); + return true; + } else { + return false; + } + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_STRING: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + uint64_t n; + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("String array too large. TinyUSDZ limites it up to {}", _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(crate::Index)); + + std::vector v(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(crate::Index), + size_t(n) * sizeof(crate::Index), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read TokenIndex array."); + return false; + } + + std::vector stringArray(static_cast(n)); + + for (size_t i = 0; i < n; i++) { + if (auto stok = GetStringToken(v[i])) { + stringArray[i] = stok.value().str(); + } else { + return false; + } + } + + DCOUT("stringArray = " << stringArray); + + // TODO: Use token type? + value->Set(stringArray); + + return true; + } else { + return false; + } + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_SPECIFIER: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_PERMISSION: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VARIABILITY: { + PUSH_ERROR("TODO: Specifier/Permission/Variability. isArray " + << rep.IsArray() << ", isCompressed " << rep.IsCompressed()); + return false; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_UCHAR: { + NON_ARRAY_UNSUPPORTED_CHECK(dty) + TODO_IMPLEMENT(dty) + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_INT: { + NON_ARRAY_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + if (!ReadIntArray(rep.IsCompressed(), &v)) { + PUSH_ERROR("Failed to read Int array."); + return false; + } + + if (v.empty()) { + PUSH_ERROR("Empty int array."); + return false; + } + + DCOUT("IntArray = " << value::print_array_snipped(v)); + + value->Set(v); + return true; + } else { + return false; + } + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_UINT: { + NON_ARRAY_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + if (!ReadIntArray(rep.IsCompressed(), &v)) { + PUSH_ERROR("Failed to read UInt array."); + return false; + } + + if (v.empty()) { + PUSH_ERROR("Empty uint array."); + return false; + } + + DCOUT("UIntArray = " << value::print_array_snipped(v)); + + value->Set(v); + return true; + } else { + return false; + } + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_INT64: { + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + if (!ReadIntArray(rep.IsCompressed(), &v)) { + PUSH_ERROR("Failed to read Int64 array."); + return false; + } + + if (v.empty()) { + PUSH_ERROR("Empty int64 array."); + return false; + } + + DCOUT("Int64Array = " << v); + + value->Set(v); + return true; + } else { + COMPRESS_UNSUPPORTED_CHECK(dty) + + CHECK_MEMORY_USAGE(sizeof(int64_t)); + + int64_t v; + if (!_sr->read(sizeof(int64_t), sizeof(int64_t), + reinterpret_cast(&v))) { + PUSH_ERROR("Failed to read int64 data."); + return false; + } + + DCOUT("int64 = " << v); + + value->Set(v); + return true; + } + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_UINT64: { + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + + if (!ReadIntArray(rep.IsCompressed(), &v)) { + PUSH_ERROR("Failed to read UInt64 array."); + return false; + } + + if (v.empty()) { + PUSH_ERROR("Empty uint64 array."); + return false; + } + + DCOUT("UInt64Array = " << value::print_array_snipped(v)); + + value->Set(v); + return true; + } else { + COMPRESS_UNSUPPORTED_CHECK(dty) + + CHECK_MEMORY_USAGE(sizeof(uint64_t)); + + uint64_t v; + if (!_sr->read(sizeof(uint64_t), sizeof(uint64_t), + reinterpret_cast(&v))) { + PUSH_ERROR("Failed to read uint64 data."); + return false; + } + + DCOUT("uint64 = " << v); + + value->Set(v); + return true; + } + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_HALF: { + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + if (!ReadHalfArray(rep.IsCompressed(), &v)) { + PUSH_ERROR("Failed to read half array value."); + return false; + } + + value->Set(v); + + return true; + } else { + PUSH_ERROR("Non-inlined, non-array Half value is invalid."); + return false; + } + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_FLOAT: { + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + if (!ReadFloatArray(rep.IsCompressed(), &v)) { + PUSH_ERROR("Failed to read float array value."); + return false; + } + + DCOUT("FloatArray = " << value::print_array_snipped(v)); + + value->Set(v); + + return true; + } else { + COMPRESS_UNSUPPORTED_CHECK(dty) + + PUSH_ERROR("Non-inlined, non-array Float value is not supported."); + return false; + } + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_DOUBLE: { + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + if (!ReadDoubleArray(rep.IsCompressed(), &v)) { + PUSH_ERROR("Failed to read Double value."); + return false; + } + + DCOUT("DoubleArray = " << value::print_array_snipped(v)); + value->Set(v); + + return true; + } else { + COMPRESS_UNSUPPORTED_CHECK(dty) + + CHECK_MEMORY_USAGE(sizeof(double)); + + double v; + if (!_sr->read_double(&v)) { + PUSH_ERROR("Failed to read Double value."); + return false; + } + + DCOUT("Double " << v); + + value->Set(v); + + return true; + } + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_MATRIX2D: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + } + + if (n == 0) { + value->Set(v); + return true; + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(value::matrix2d)); + + + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(value::matrix2d), + size_t(n) * sizeof(value::matrix2d), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read Matrix2d array."); + return false; + } + + value->Set(v); + + } else { + static_assert(sizeof(value::matrix2d) == (8 * 4), ""); + + CHECK_MEMORY_USAGE(sizeof(value::matrix2d)); + + value::matrix4d v; + if (!_sr->read(sizeof(value::matrix2d), sizeof(value::matrix2d), + reinterpret_cast(v.m))) { + _err += "Failed to read value of `matrix2d` type\n"; + return false; + } + + DCOUT("value.matrix2d = " << v); + + value->Set(v); + } + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_MATRIX3D: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + } + + if (n == 0) { + value->Set(v); + return true; + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(value::matrix3d)); + + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(value::matrix3d), + size_t(n) * sizeof(value::matrix3d), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read Matrix3d array."); + return false; + } + + value->Set(v); + + } else { + static_assert(sizeof(value::matrix3d) == (8 * 9), ""); + + CHECK_MEMORY_USAGE(sizeof(value::matrix3d)); + + value::matrix3d v; + if (!_sr->read(sizeof(value::matrix3d), sizeof(value::matrix3d), + reinterpret_cast(v.m))) { + _err += "Failed to read value of `matrix3d` type\n"; + return false; + } + + DCOUT("value.matrix3d = " << v); + + value->Set(v); + } + + return true; + } + + case crate::CrateDataTypeId::CRATE_DATA_TYPE_MATRIX4D: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + } + + if (n == 0) { + value->Set(v); + return true; + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(value::matrix4d)); + + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(value::matrix4d), + size_t(n) * sizeof(value::matrix4d), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read Matrix4d array."); + return false; + } + + value->Set(v); + + } else { + static_assert(sizeof(value::matrix4d) == (8 * 16), ""); + + CHECK_MEMORY_USAGE(sizeof(value::matrix4d)); + + value::matrix4d v; + if (!_sr->read(sizeof(value::matrix4d), sizeof(value::matrix4d), + reinterpret_cast(v.m))) { + _err += "Failed to read value of `matrix4d` type\n"; + return false; + } + + DCOUT("value.matrix4d = " << v); + + value->Set(v); + } + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_QUATD: { + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + } + + if (n == 0) { + value->Set(v); + return true; + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(value::quatd)); + + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(value::quatd), + size_t(n) * sizeof(value::quatd), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read Quatf array."); + return false; + } + + DCOUT("Quatf[] = " << v); + + value->Set(v); + + } else { + COMPRESS_UNSUPPORTED_CHECK(dty) + + CHECK_MEMORY_USAGE(sizeof(value::quatd)); + + value::quatd v; + if (!_sr->read(sizeof(value::quatd), sizeof(value::quatd), + reinterpret_cast(&v))) { + _err += "Failed to read Quatd value\n"; + return false; + } + + DCOUT("Quatd = " << v); + value->Set(v); + } + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_QUATF: { + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + } + + if (n == 0) { + value->Set(v); + return true; + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(value::quatf)); + + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(value::quatf), + size_t(n) * sizeof(value::quatf), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read Quatf array."); + return false; + } + + DCOUT("Quatf[] = " << v); + + value->Set(v); + + } else { + COMPRESS_UNSUPPORTED_CHECK(dty) + + CHECK_MEMORY_USAGE(sizeof(value::quatf)); + + value::quatf v; + if (!_sr->read(sizeof(value::quatf), sizeof(value::quatf), + reinterpret_cast(&v))) { + _err += "Failed to read Quatf value\n"; + return false; + } + + DCOUT("Quatf = " << v); + value->Set(v); + } + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_QUATH: { + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + } + + if (n == 0) { + value->Set(v); + return true; + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(value::quath)); + + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(value::quath), + size_t(n) * sizeof(value::quath), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read Quath array."); + return false; + } + + DCOUT("Quath[] = " << v); + + value->Set(v); + + } else { + COMPRESS_UNSUPPORTED_CHECK(dty) + + CHECK_MEMORY_USAGE(sizeof(value::quath)); + + value::quath v; + if (!_sr->read(sizeof(value::quath), sizeof(value::quath), + reinterpret_cast(&v))) { + _err += "Failed to read Quath value\n"; + return false; + } + + DCOUT("Quath = " << v); + value->Set(v); + } + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC2D: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + } + + if (n == 0) { + value->Set(v); + return true; + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(value::double2)); + + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(value::double2), + size_t(n) * sizeof(value::double2), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read double2 array."); + return false; + } + + DCOUT("double2[] = " << value::print_array_snipped(v)); + + value->Set(v); + return true; + } else { + CHECK_MEMORY_USAGE(sizeof(value::double2)); + value::double2 v; + if (!_sr->read(sizeof(value::double2), sizeof(value::double2), + reinterpret_cast(&v))) { + PUSH_ERROR("Failed to read double2 data."); + return false; + } + + DCOUT("double2 = " << v); + + value->Set(v); + return true; + } + } + + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC2F: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + if (n == 0) { + value->Set(v); + return true; + } + + CHECK_MEMORY_USAGE(n * sizeof(value::float2)); + + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(value::float2), + size_t(n) * sizeof(value::float2), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read float2 array."); + return false; + } + + DCOUT("float2[] = " << value::print_array_snipped(v)); + + value->Set(v); + return true; + } else { + CHECK_MEMORY_USAGE(sizeof(value::float2)); + value::float2 v; + if (!_sr->read(sizeof(value::float2), sizeof(value::float2), + reinterpret_cast(&v))) { + PUSH_ERROR("Failed to read float2 data."); + return false; + } + + DCOUT("float2 = " << v); + + value->Set(v); + return true; + } + } + + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC2H: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(value::half2)); + + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(value::half2), + size_t(n) * sizeof(value::half2), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read half2 array."); + return false; + } + + DCOUT("half2[] = " << value::print_array_snipped(v)); + value->Set(v); + + } else { + CHECK_MEMORY_USAGE(sizeof(value::half2)); + value::half2 v; + if (!_sr->read(sizeof(value::half2), sizeof(value::half2), + reinterpret_cast(&v))) { + PUSH_ERROR("Failed to read half2"); + return false; + } + + DCOUT("half2 = " << v); + + value->Set(v); + } + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC2I: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(value::int2)); + + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(value::int2), + size_t(n) * sizeof(value::int2), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read int2 array."); + return false; + } + + DCOUT("int2[] = " << value::print_array_snipped(v)); + value->Set(v); + + } else { + CHECK_MEMORY_USAGE(sizeof(value::int2)); + value::int2 v; + if (!_sr->read(sizeof(value::int2), sizeof(value::int2), + reinterpret_cast(&v))) { + PUSH_ERROR("Failed to read int2"); + return false; + } + + DCOUT("int2 = " << v); + + value->Set(v); + } + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC3D: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(value::double3)); + + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(value::double3), + size_t(n) * sizeof(value::double3), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read double3 array."); + return false; + } + + DCOUT("double3[] = " << value::print_array_snipped(v)); + value->Set(v); + + } else { + CHECK_MEMORY_USAGE(sizeof(value::double3)); + value::double3 v; + if (!_sr->read(sizeof(value::double3), sizeof(value::double3), + reinterpret_cast(&v))) { + PUSH_ERROR("Failed to read double3"); + return false; + } + + DCOUT("double3 = " << v); + + value->Set(v); + } + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC3F: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(value::float3)); + + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(value::float3), + size_t(n) * sizeof(value::float3), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read float3 array."); + return false; + } + + DCOUT("float3f[] = " << value::print_array_snipped(v)); + value->Set(v); + + } else { + CHECK_MEMORY_USAGE(sizeof(value::float3)); + value::float3 v; + if (!_sr->read(sizeof(value::float3), sizeof(value::float3), + reinterpret_cast(&v))) { + PUSH_ERROR("Failed to read float3"); + return false; + } + + DCOUT("float3 = " << v); + + value->Set(v); + } + + return true; + } + + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC3H: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(value::half3)); + + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(value::half3), + size_t(n) * sizeof(value::half3), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read half3 array."); + return false; + } + + DCOUT("half3[] = " << value::print_array_snipped(v)); + value->Set(v); + + } else { + CHECK_MEMORY_USAGE(sizeof(value::half3)); + value::half3 v; + if (!_sr->read(sizeof(value::half3), sizeof(value::half3), + reinterpret_cast(&v))) { + PUSH_ERROR("Failed to read half3"); + return false; + } + + DCOUT("half3 = " << v); + + value->Set(v); + } + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC3I: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(value::int3)); + + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(value::int3), + size_t(n) * sizeof(value::int3), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read int3 array."); + return false; + } + + DCOUT("int3[] = " << value::print_array_snipped(v)); + value->Set(v); + + } else { + CHECK_MEMORY_USAGE(sizeof(value::int3)); + value::int3 v; + if (!_sr->read(sizeof(value::int3), sizeof(value::int3), + reinterpret_cast(&v))) { + PUSH_ERROR("Failed to read int3"); + return false; + } + + DCOUT("int3 = " << v); + + value->Set(v); + } + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC4D: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(value::double4)); + + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(value::double4), + size_t(n) * sizeof(value::double4), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read double4 array."); + return false; + } + + DCOUT("double4[] = " << value::print_array_snipped(v)); + value->Set(v); + + } else { + CHECK_MEMORY_USAGE(sizeof(value::double4)); + value::double4 v; + if (!_sr->read(sizeof(value::double4), sizeof(value::double4), + reinterpret_cast(&v))) { + PUSH_ERROR("Failed to read double4"); + return false; + } + + DCOUT("double4 = " << v); + + value->Set(v); + } + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC4F: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(value::float4)); + + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(value::float4), + size_t(n) * sizeof(value::float4), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read float4 array."); + return false; + } + + DCOUT("float4[] = " << value::print_array_snipped(v)); + value->Set(v); + + } else { + CHECK_MEMORY_USAGE(sizeof(value::float4)); + value::float4 v; + if (!_sr->read(sizeof(value::float4), sizeof(value::float4), + reinterpret_cast(&v))) { + PUSH_ERROR("Failed to read float4"); + return false; + } + + DCOUT("float4 = " << v); + + value->Set(v); + } + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC4H: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(value::half4)); + + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(value::half4), + size_t(n) * sizeof(value::half4), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read half4 array."); + return false; + } + + DCOUT("half4[] = " << value::print_array_snipped(v)); + value->Set(v); + + } else { + CHECK_MEMORY_USAGE(sizeof(value::half4)); + value::half4 v; + if (!_sr->read(sizeof(value::half4), sizeof(value::half4), + reinterpret_cast(&v))) { + PUSH_ERROR("Failed to read half4"); + return false; + } + + DCOUT("half4 = " << v); + + value->Set(v); + } + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VEC4I: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + if (rep.IsArray()) { + std::vector v; + if (rep.GetPayload() == 0) { // empty array + value->Set(v); + return true; + } + uint64_t n{0}; + if (VERSION_LESS_THAN_0_8_0(_version)) { + uint32_t shapesize; // not used + if (!_sr->read4(&shapesize)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + uint32_t _n; + if (!_sr->read4(&_n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + n = _n; + } else { + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(value::int4)); + + v.resize(static_cast(n)); + if (!_sr->read(size_t(n) * sizeof(value::int4), + size_t(n) * sizeof(value::int4), + reinterpret_cast(v.data()))) { + PUSH_ERROR("Failed to read int4 array."); + return false; + } + + DCOUT("int4[] = " << value::print_array_snipped(v)); + value->Set(v); + + } else { + CHECK_MEMORY_USAGE(sizeof(value::int4)); + value::int4 v; + if (!_sr->read(sizeof(value::int4), sizeof(value::int4), + reinterpret_cast(&v))) { + PUSH_ERROR("Failed to read int4"); + return false; + } + + DCOUT("int4 = " << v); + + value->Set(v); + } + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_DICTIONARY: { + COMPRESS_UNSUPPORTED_CHECK(dty) + ARRAY_UNSUPPORTED_CHECK(dty) + + //crate::CrateValue::Dictionary dict; + CustomDataType dict; + + if (!ReadCustomData(&dict)) { + _err += "Failed to read Dictionary value\n"; + return false; + } + + DCOUT("Dict. nelems = " << dict.size()); + + value->Set(dict); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_TOKEN_LIST_OP: { + ListOp lst; + + if (!ReadTokenListOp(&lst)) { + PUSH_ERROR("Failed to read TokenListOp data"); + return false; + } + + value->Set(lst); + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_PATH_LIST_OP: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + // SdfListOp + // => underliying storage is the array of ListOp[PathIndex] + ListOp lst; + + if (!ReadPathListOp(&lst)) { + PUSH_ERROR("Failed to read PathListOp data."); + return false; + } + + value->Set(lst); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_STRING_LIST_OP: { + ListOp lst; + + if (!ReadStringListOp(&lst)) { + PUSH_ERROR("Failed to read StringListOp data"); + return false; + } + + value->Set(lst); + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_PATH_VECTOR: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + std::vector v; + if (!ReadPathArray(&v)) { + _err += "Failed to read PathVector value\n"; + return false; + } + + DCOUT("PathVector = " << to_string(v)); + + value->Set(v); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_TOKEN_VECTOR: { + COMPRESS_UNSUPPORTED_CHECK(dty) + // std::vector + uint64_t n{0}; + if (!_sr->read8(&n)) { + PUSH_ERROR("Failed to read the number of array elements."); + return false; + } + + if (n > _config.maxArrayElements) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Array size {} too large. maxArrayElements is set to {}. Please increase maxArrayElements in CrateReaderConfig.", n, _config.maxArrayElements)); + } + + CHECK_MEMORY_USAGE(n * sizeof(crate::Index)); + + std::vector indices(static_cast(n)); + if (!_sr->read(static_cast(n) * sizeof(crate::Index), + static_cast(n) * sizeof(crate::Index), + reinterpret_cast(indices.data()))) { + PUSH_ERROR("Failed to read TokenVector value."); + return false; + } + + DCOUT("TokenVector(index) = " << indices); + + std::vector tokens(indices.size()); + for (size_t i = 0; i < indices.size(); i++) { + if (auto tokv = GetToken(indices[i])) { + tokens[i] = tokv.value(); + } else { + return false; + } + } + + DCOUT("TokenVector = " << tokens); + + value->Set(tokens); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_TIME_SAMPLES: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + value::TimeSamples ts; + if (!ReadTimeSamples(&ts)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read TimeSamples data"); + } + + value->Set(ts); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_DOUBLE_VECTOR: { + std::vector v; + if (!ReadDoubleArray(rep.IsCompressed(), &v)) { + _err += "Failed to read DoubleVector value\n"; + return false; + } + + DCOUT("DoubleArray = " << v); + + value->Set(v); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_STRING_VECTOR: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + std::vector v; + if (!ReadStringArray(&v)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read StringVector value"); + } + + DCOUT("StringArray = " << v); + + value->Set(v); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VARIANT_SELECTION_MAP: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + VariantSelectionMap m; + if (!ReadVariantSelectionMap(&m)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read VariantSelectionMap value"); + } + + DCOUT("VariantSelectionMap = " << print_variantSelectionMap(m, 0)); + + value->Set(m); + + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_LAYER_OFFSET_VECTOR: { + COMPRESS_UNSUPPORTED_CHECK(dty) + // LayerOffset[] + + std::vector v; + if (!ReadLayerOffsetArray(&v)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read LayerOffsetVector value"); + } + + DCOUT("LayerOffsetVector = " << v); + + value->Set(v); + + return true; + + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_PAYLOAD: { + COMPRESS_UNSUPPORTED_CHECK(dty) + + // Payload + Payload v; + if (!ReadPayload(&v)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read Payload value"); + } + + DCOUT("Payload = " << v); + + value->Set(v); + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_PAYLOAD_LIST_OP: { + ListOp lst; + + if (!ReadListOp(&lst)) { + PUSH_ERROR("Failed to read PayloadListOp data"); + return false; + } + + value->Set(lst); + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_REFERENCE_LIST_OP: { + ListOp lst; + + if (!ReadListOp(&lst)) { + PUSH_ERROR("Failed to read ReferenceListOp data"); + return false; + } + + value->Set(lst); + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_INT_LIST_OP: { + ListOp lst; + + if (!ReadListOp(&lst)) { + PUSH_ERROR("Failed to read IntListOp data"); + return false; + } + + value->Set(lst); + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_INT64_LIST_OP: { + ListOp lst; + + if (!ReadListOp(&lst)) { + PUSH_ERROR("Failed to read Int64ListOp data"); + return false; + } + + value->Set(lst); + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_UINT_LIST_OP: { + ListOp lst; + + if (!ReadListOp(&lst)) { + PUSH_ERROR("Failed to read UIntListOp data"); + return false; + } + + value->Set(lst); + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_UINT64_LIST_OP: { + ListOp lst; + + if (!ReadListOp(&lst)) { + PUSH_ERROR("Failed to read UInt64ListOp data"); + return false; + } + + value->Set(lst); + return true; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VALUE_BLOCK: { + PUSH_ERROR( + "ValueBlock must be defined in Inlined ValueRep."); + return false; + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_VALUE: { + + crate::ValueRep local_rep{0}; + if (!ReadValueRep(&local_rep)) { + PUSH_ERROR( + "Failed to read ValueRep for VALUE type."); + return false; + } + + if (unpackRecursionGuard.size() > _config.maxValueRecursion) { + // To many recursive stacks. We report error + PUSH_ERROR( + "Too many recursion when decoding generic VALUE data."); + return false; + } + + // TODO: use crate::ValueRep for set container type. + if (unpackRecursionGuard.count(local_rep.GetData())) { + // Recursion detected. + PUSH_ERROR( + "Corrupted Value data detected."); + return false; + } else { + crate::CrateValue local_val; + bool ret = UnpackValueRep(local_rep, &local_val); + if (!ret) { + return false; + } + + (*value) = local_val; + + unpackRecursionGuard.erase(local_rep.GetData()); + return true; + } + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_UNREGISTERED_VALUE: { + COMPRESS_UNSUPPORTED_CHECK(dty) + ARRAY_UNSUPPORTED_CHECK(dty) + + // 8byte for the offset for recursive value. See RecursiveRead() in + // https://github.com/PixarAnimationStudios/USD/blob/release/pxr/usd/usd/crateFile.cpp for details. + int64_t local_offset{0}; + if (!_sr->read8(&local_offset)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read the offset for value in Dictionary."); + return false; + } + + DCOUT("UnregisteredValue offset = " << local_offset); + DCOUT("tell = " << _sr->tell()); + + // -8 to compensate sizeof(offset) + if (!_sr->seek_from_current(local_offset - 8)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to seek to UNREGISTERD_VALUE content. Invalid offset value: " + + std::to_string(local_offset)); + } + + uint64_t saved_position = _sr->tell(); + + crate::ValueRep local_rep{0}; + if (!ReadValueRep(&local_rep)) { + PUSH_ERROR( + "Failed to read ValueRep for UNREGISTERED_VALUE type."); + return false; + } + + auto local_tyRet = crate::GetCrateDataType(local_rep.GetType()); + if (!local_tyRet) { + PUSH_ERROR(local_tyRet.error()); + return false; + } + + const auto local_dty = local_tyRet.value(); + + // Should be STRING or DICTIONARY for UNREGISTERED_VALUE. + if (local_dty.dtype_id == crate::CrateDataTypeId::CRATE_DATA_TYPE_STRING) { + COMPRESS_UNSUPPORTED_CHECK(local_dty) + ARRAY_UNSUPPORTED_CHECK(local_dty) + + if (local_rep.IsInlined()) { + uint32_t local_d = (local_rep.GetPayload() & ((1ull << (sizeof(uint32_t) * 8)) - 1)); + if (auto v = GetStringToken(crate::Index(local_d))) { + std::string str = v.value().str(); + + DCOUT("UNREGISTERED_VALUE.string = " << str); + + // NOTE: string may contain double-quotes. + // We remove it at here, but it'd be better not to do it. + std::string unquoted = unwrap(str); + value->Set(unquoted); + + if (!_sr->seek_set(saved_position)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to set seek."); + } + return true; + } else { + PUSH_ERROR("Failed to decode String."); + return false; + } + } else { + PUSH_ERROR("String value must be inlined."); + return false; + } + + } else if (local_dty.dtype_id == crate::CrateDataTypeId::CRATE_DATA_TYPE_DICTIONARY) { + COMPRESS_UNSUPPORTED_CHECK(local_dty) + ARRAY_UNSUPPORTED_CHECK(local_dty) + + CustomDataType dict; + + if (local_rep.IsInlined()) { + // empty dict + } else{ + if (!ReadCustomData(&dict)) { + _err += "Failed to read Dictionary value\n"; + return false; + } + } + value->Set(dict); + if (!_sr->seek_set(saved_position)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to set seek."); + } + return true; + + } else { + PUSH_ERROR_AND_RETURN(fmt::format("UNREGISTERD_VALUE type must be string or dictionary, but got other data type: {}(id {}).", GetCrateDataTypeName(local_dty.dtype_id), local_rep.GetType())); + } + + } + case crate::CrateDataTypeId::CRATE_DATA_TYPE_UNREGISTERED_VALUE_LIST_OP: + case crate::CrateDataTypeId::CRATE_DATA_TYPE_TIME_CODE: { + PUSH_ERROR( + "Invalid data type(or maybe not supported in TinyUSDZ yet) for " + "Uninlined value: " + + crate::GetCrateDataTypeName(dty.dtype_id)); + return false; + } + } + +#undef TODO_IMPLEMENT +#undef COMPRESS_UNSUPPORTED_CHECK +#undef NON_ARRAY_UNSUPPORTED_CHECK + + // Never should reach here. + return false; +} + +#if defined(TINYUSDZ_CRATE_USE_FOR_BASED_PATH_INDEX_DECODER) +bool CrateReader::BuildDecompressedPathsImpl( + BuildDecompressedPathsArg *arg) { + + if (!arg) { + return false; + } + + Path parentPath = arg->parentPath; + if (!arg->pathIndexes) { + return false; + } + if (!arg->elementTokenIndexes) { + return false; + } + if (!arg->jumps) { + return false; + } + if (!arg->visit_table) { + return false; + } + auto &pathIndexes = *arg->pathIndexes; + auto &elementTokenIndexes = *arg->elementTokenIndexes; + auto &jumps = *arg->jumps; + auto &visit_table = *arg->visit_table; + + auto rootPath = Path::make_root_path(); + + const size_t maxIter = _config.maxPathIndicesDecodeIteration; + + std::stack startIndexStack; + std::stack endIndexStack; + std::stack parentPathStack; + + size_t nIter = 0; + + size_t startIndex = arg->startIndex; + size_t endIndex = arg->endIndex; + + while (nIter < maxIter) { + + DCOUT("startIndex = " << startIndex << ", endIdx = " << endIndex); + + for (size_t thisIndex = startIndex; thisIndex < (endIndex + 1); thisIndex++) { + //auto thisIndex = curIndex++; + DCOUT("thisIndex = " << thisIndex << ", pathIndexes.size = " << pathIndexes.size()); + if (parentPath.is_empty()) { + // root node. + // Assume single root node in the scene. + DCOUT("paths[" << pathIndexes[thisIndex] + << "] is parent. name = " << parentPath.full_path_name()); + parentPath = rootPath; + + if (thisIndex >= pathIndexes.size()) { + PUSH_ERROR("Index exceeds pathIndexes.size()"); + return false; + } + + size_t idx = pathIndexes[thisIndex]; + if (idx >= _paths.size()) { + PUSH_ERROR("Index is out-of-range"); + return false; + } + + if (idx < visit_table.size()) { + if (visit_table[idx]) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Circular referencing of Path index {}(thisIndex {}) detected. Invalid Paths data.", idx, thisIndex)); + } + } + + _paths[idx] = parentPath; + visit_table[idx] = true; + } else { + if (thisIndex >= elementTokenIndexes.size()) { + PUSH_ERROR("Index exceeds elementTokenIndexes.size()"); + return false; + } + int32_t _tokenIndex = elementTokenIndexes[thisIndex]; + DCOUT("elementTokenIndex = " << _tokenIndex); + bool isPrimPropertyPath = _tokenIndex < 0; + // ~0 returns -2147483648, so cast to uint32 + uint32_t tokenIndex = uint32_t(isPrimPropertyPath ? -_tokenIndex : _tokenIndex); + + DCOUT("tokenIndex = " << tokenIndex << ", _tokens.size = " << _tokens.size()); + if (tokenIndex >= _tokens.size()) { + PUSH_ERROR("Invalid tokenIndex in BuildDecompressedPathsImpl."); + return false; + } + auto const &elemToken = _tokens[size_t(tokenIndex)]; + DCOUT("elemToken = " << elemToken); + DCOUT("[" << pathIndexes[thisIndex] << "].append = " << elemToken); + + size_t idx = pathIndexes[thisIndex]; + if (idx >= _paths.size()) { + PUSH_ERROR("Index is out-of-range"); + return false; + } + + if (idx >= _elemPaths.size()) { + PUSH_ERROR("Index is out-of-range"); + return false; + } + + if (idx < visit_table.size()) { + if (visit_table[idx]) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Circular referencing of Path index {}(thisIndex {}) detected. Invalid Paths data.", idx, thisIndex)); + } + } + + // Reconstruct full path + _paths[idx] = + isPrimPropertyPath ? parentPath.AppendProperty(elemToken.str()) + : parentPath.AppendElement(elemToken.str()); // prim, variantSelection, etc. + + // also set leaf path for 'primChildren' check + _elemPaths[idx] = Path(elemToken.str(), ""); + //_paths[pathIndexes[thisIndex]].SetLocalPart(elemToken.str()); + + visit_table[idx] = true; + } + + // If we have either a child or a sibling but not both, then just + // continue to the neighbor. If we have both then spawn a task for the + // sibling and do the child ourself. We think that our path trees tend + // to be broader more often than deep. + + if (thisIndex >= jumps.size()) { + PUSH_ERROR("Index is out-of-range"); + return false; + } + + bool hasChild = (jumps[thisIndex] > 0) || (jumps[thisIndex] == -1); + bool hasSibling = (jumps[thisIndex] >= 0); + DCOUT("hasChild = " << hasChild << ", hasSibling = " << hasSibling); + + if (hasChild) { + if (hasSibling) { + // NOTE(syoyo): This recursive call can be parallelized + auto siblingIndex = thisIndex + size_t(jumps[thisIndex]); + + if (siblingIndex >= jumps.size()) { + PUSH_ERROR_AND_RETURN("jump index corrupted."); + } + + // Find subtree end. + size_t subtreeStartIdx = siblingIndex; + size_t subtreeIdx = subtreeStartIdx; + + for (; subtreeIdx < jumps.size(); subtreeIdx++) { + + bool has_child = (jumps[subtreeIdx] > 0) || (jumps[subtreeIdx] == -1); + bool has_sibling = (jumps[subtreeIdx] >= 0); + + if (has_child || has_sibling) { + continue; + } + break; + } + + size_t subtreeEndIdx = subtreeIdx; + if (subtreeEndIdx >= jumps.size()) { + // Guess corrupted. + PUSH_ERROR_AND_RETURN("jump indices seems corrupted."); + } + + DCOUT("subtree startIdx " << subtreeStartIdx << ", subtree endIndex " << subtreeEndIdx); + + if (subtreeEndIdx >= subtreeStartIdx) { + + // index range after traversing subtree + if (jumps[thisIndex] > 1) { + + // Setup stacks to resume loop from [Cont.] + startIndexStack.push(thisIndex+1); + // jumps should be always positive, so no siblingIndex < thisIndex + endIndexStack.push(siblingIndex-1); // endIndex is inclusive so subtract 1. + + { + size_t idx = pathIndexes[thisIndex]; + if (idx >= _paths.size()) { + PUSH_ERROR("Index is out-of-range"); + return false; + } + + parentPathStack.push(_paths[idx]); + } + } + + startIndexStack.push(subtreeStartIdx); + endIndexStack.push(subtreeEndIdx); + + parentPathStack.push(parentPath); + DCOUT("stack size: " << startIndexStack.size()); + + nIter++; + + break; // goto `(A)` + } + + } + + // [Cont.] + size_t idx = pathIndexes[thisIndex]; + if (idx >= _paths.size()) { + PUSH_ERROR("Index is out-of-range"); + return false; + } + + parentPath = _paths[idx]; + + } + } + + // (A) + + if (startIndexStack.empty()) { + break; // end traversal + } + + startIndex = startIndexStack.top(); + startIndexStack.pop(); + + endIndex = endIndexStack.top(); + endIndexStack.pop(); + + parentPath = parentPathStack.top(); + parentPathStack.pop(); + + nIter++; + } + + if (nIter >= maxIter) { + PUSH_ERROR_AND_RETURN("PathIndex tree Too deep."); + } + + return true; +} +#else +bool CrateReader::BuildDecompressedPathsImpl( + std::vector const &pathIndexes, + std::vector const &elementTokenIndexes, + std::vector const &jumps, + std::vector &visit_table, + size_t curIndex, const Path &_parentPath) { + + Path parentPath = _parentPath; + + bool hasChild = false, hasSibling = false; + do { + auto thisIndex = curIndex++; + DCOUT("thisIndex = " << thisIndex << ", pathIndexes.size = " << pathIndexes.size()); + if (parentPath.is_empty()) { + // root node. + // Assume single root node in the scene. + DCOUT("paths[" << pathIndexes[thisIndex] + << "] is parent. name = " << parentPath.full_path_name()); + parentPath = Path::make_root_path(); + + if (thisIndex >= pathIndexes.size()) { + PUSH_ERROR("Index exceeds pathIndexes.size()"); + return false; + } + + size_t idx = pathIndexes[thisIndex]; + if (idx >= _paths.size()) { + PUSH_ERROR("Index is out-of-range"); + return false; + } + + if (idx < visit_table.size()) { + if (visit_table[idx]) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Circular referencing of Path index tree detected. Invalid Paths data."); + } + } + + _paths[idx] = parentPath; + visit_table[idx] = true; + } else { + if (thisIndex >= elementTokenIndexes.size()) { + PUSH_ERROR("Index exceeds elementTokenIndexes.size()"); + return false; + } + int32_t _tokenIndex = elementTokenIndexes[thisIndex]; + DCOUT("elementTokenIndex = " << _tokenIndex); + bool isPrimPropertyPath = _tokenIndex < 0; + // ~0 returns -2147483648, so cast to uint32 + uint32_t tokenIndex = uint32_t(isPrimPropertyPath ? -_tokenIndex : _tokenIndex); + + DCOUT("tokenIndex = " << tokenIndex << ", _tokens.size = " << _tokens.size()); + if (tokenIndex >= _tokens.size()) { + PUSH_ERROR("Invalid tokenIndex in BuildDecompressedPathsImpl."); + return false; + } + auto const &elemToken = _tokens[size_t(tokenIndex)]; + DCOUT("elemToken = " << elemToken); + DCOUT("[" << pathIndexes[thisIndex] << "].append = " << elemToken); + + size_t idx = pathIndexes[thisIndex]; + if (idx >= _paths.size()) { + PUSH_ERROR("Index is out-of-range"); + return false; + } + + if (idx >= _elemPaths.size()) { + PUSH_ERROR("Index is out-of-range"); + return false; + } + + if (idx < visit_table.size()) { + if (visit_table[idx]) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Circular referencing of Path index tree detected. Invalid Paths data."); + } + } + + // Reconstruct full path + _paths[idx] = + isPrimPropertyPath ? parentPath.AppendProperty(elemToken.str()) + : parentPath.AppendElement(elemToken.str()); // prim, variantSelection, etc. + + // also set leaf path for 'primChildren' check + _elemPaths[idx] = Path(elemToken.str(), ""); + //_paths[pathIndexes[thisIndex]].SetLocalPart(elemToken.str()); + + visit_table[idx] = true; + } + + // If we have either a child or a sibling but not both, then just + // continue to the neighbor. If we have both then spawn a task for the + // sibling and do the child ourself. We think that our path trees tend + // to be broader more often than deep. + + if (thisIndex >= jumps.size()) { + PUSH_ERROR("Index is out-of-range"); + return false; + } + + hasChild = (jumps[thisIndex] > 0) || (jumps[thisIndex] == -1); + hasSibling = (jumps[thisIndex] >= 0); + DCOUT("hasChild = " << hasChild << ", hasSibling = " << hasSibling); + + DCOUT(fmt::format("hasChild {}, hasSibling {}", hasChild, hasSibling)); + + if (hasChild) { + if (hasSibling) { + // NOTE(syoyo): This recursive call can be parallelized + auto siblingIndex = thisIndex + size_t(jumps[thisIndex]); + if (!BuildDecompressedPathsImpl(pathIndexes, elementTokenIndexes, jumps, visit_table, + siblingIndex, parentPath)) { + return false; + } + } + + size_t idx = pathIndexes[thisIndex]; + if (idx >= _paths.size()) { + PUSH_ERROR("Index is out-of-range"); + return false; + } + + // Have a child (may have also had a sibling). Reset parent path. + parentPath = _paths[idx]; + } + // If we had only a sibling, we just continue since the parent path is + // unchanged and the next thing in the reader stream is the sibling's + // header. + } while (hasChild || hasSibling); + + return true; +} +#endif + +#if defined(TINYUSDZ_CRATE_USE_FOR_BASED_PATH_INDEX_DECODER) +bool CrateReader::BuildNodeHierarchy( + std::vector const &pathIndexes, + std::vector const &elementTokenIndexes, + std::vector const &jumps, + std::vector &visit_table, /* inout */ + size_t _curIndex, + int64_t _parentNodeIndex) { + + (void)elementTokenIndexes; + + std::stack parentNodeIndexStack; + std::stack startIndexStack; + std::stack endIndexStack; + + size_t nIter = 0; + const size_t maxIter = _config.maxPathIndicesDecodeIteration; + + size_t startIndex = _curIndex; + size_t endIndex = pathIndexes.size() - 1; + int64_t parentNodeIndex = _parentNodeIndex; + + // NOTE: Need to indirectly lookup index through pathIndexes[] when accessing + // `_nodes` + while (nIter < maxIter) { + + for (size_t thisIndex = startIndex; thisIndex < (endIndex + 1); thisIndex++) { + if (parentNodeIndex == -1) { + // root node. + // Assume single root node in the scene. + //assert(thisIndex == 0); + if (thisIndex != 0) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "TODO: Multiple root nodes."); + } + + if (thisIndex >= pathIndexes.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Index out-of-range."); + } + + size_t pathIdx = pathIndexes[thisIndex]; + if (pathIdx >= _paths.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "PathIndex out-of-range."); + } + + if (pathIdx >= _nodes.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "PathIndex out-of-range."); + } + + if (pathIdx >= visit_table.size()) { + // This should not be happan though + PUSH_ERROR_AND_RETURN_TAG(kTag, "[InternalError] out-of-range."); + } + + if (visit_table[pathIdx]) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Circular referencing detected. Invalid Prim tree representation."); + } + + _nodes[pathIdx] = Node(parentNodeIndex, _paths[pathIdx]); + visit_table[pathIdx] = true; + + parentNodeIndex = int64_t(thisIndex); + + } else { + //if (parentNodeIndex >= int64_t(_nodes.size())) { + // PUSH_ERROR_AND_RETURN_TAG(kTag, "Parent Index out-of-range."); + //} + + if (parentNodeIndex >= int64_t(pathIndexes.size())) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Parent Index out-of-range."); + } + + if (thisIndex >= pathIndexes.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Index out-of-range."); + } + + DCOUT("Hierarchy. parent[" << pathIndexes[size_t(parentNodeIndex)] + << "].add_child = " << pathIndexes[thisIndex]); + + size_t pathIdx = pathIndexes[thisIndex]; + if (pathIdx >= _paths.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "PathIndex out-of-range."); + } + + if (pathIdx >= _nodes.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "PathIndex out-of-range."); + } + + if (pathIdx >= visit_table.size()) { + // This should not be happan though + PUSH_ERROR_AND_RETURN_TAG(kTag, "[InternalError] out-of-range."); + } + + if (visit_table[pathIdx]) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Circular referencing detected. Invalid Prim tree representation."); + } + + + // Ensure parent is not set yet. + if (_nodes[pathIdx].GetParent() != -2) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "???: Maybe corrupted path hierarchy?."); + } + + Node node(parentNodeIndex, _paths[pathIdx]); + _nodes[pathIdx] = node; + + visit_table[pathIdx] = true; + + if (pathIdx >= _elemPaths.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "PathIndex out-of-range."); + } + + //std::string name = _paths[pathIndexes[thisIndex]].local_path_name(); + std::string name = _elemPaths[pathIdx].full_path_name(); + DCOUT("childName = " << name); + + size_t parentNodeIdx = size_t(parentNodeIndex); + if (parentNodeIdx >= pathIndexes.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "ParentNodeIdx out-of-range."); + } + + size_t parentPathIdx = pathIndexes[parentNodeIdx]; + if (parentPathIdx >= _nodes.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "PathIndex out-of-range."); + } + + if (!_nodes[parentPathIdx].AddChildren( + name, pathIdx)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid path index."); + } + } + + if (thisIndex >= jumps.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Index is out-of-range"); + } + + bool hasChild = (jumps[thisIndex] > 0) || (jumps[thisIndex] == -1); + bool hasSibling = (jumps[thisIndex] >= 0); + + if (hasChild) { + if (hasSibling) { + auto siblingIndex = thisIndex + size_t(jumps[thisIndex]); + + if (siblingIndex >= jumps.size()) { + PUSH_ERROR_AND_RETURN("jump index corrupted."); + } + + // Find subtree end. + size_t subtreeStartIdx = siblingIndex; + size_t subtreeIdx = subtreeStartIdx; + + for (; subtreeIdx < jumps.size(); subtreeIdx++) { + + bool has_child = (jumps[subtreeIdx] > 0) || (jumps[subtreeIdx] == -1); + bool has_sibling = (jumps[subtreeIdx] >= 0); + + if (has_child || has_sibling) { + continue; + } + break; + } + + size_t subtreeEndIdx = subtreeIdx; + if (subtreeEndIdx >= jumps.size()) { + // Guess corrupted. + PUSH_ERROR_AND_RETURN("jump indices seems corrupted."); + } + + DCOUT("subtree startIdx " << subtreeStartIdx << ", subtree endIndex " << subtreeEndIdx); + + if (subtreeEndIdx >= subtreeStartIdx) { + + // index range after traversing subtree + if (jumps[thisIndex] > 1) { + startIndexStack.push(thisIndex+1); + // jumps should be always positive, so no siblingIndex < thisIndex + endIndexStack.push(siblingIndex-1); // endIndex is inclusive so subtract 1. + parentNodeIndexStack.push(int64_t(thisIndex)); + } + + startIndexStack.push(subtreeStartIdx); + endIndexStack.push(subtreeEndIdx); + parentNodeIndexStack.push(parentNodeIndex); + + DCOUT("stack size: " << startIndexStack.size()); + + nIter++; + + break; // goto `(A)` + } + + } + // Have a child (may have also had a sibling). Reset parent node index + parentNodeIndex = int64_t(thisIndex); + DCOUT("parentNodeIndex = " << parentNodeIndex); + } + } + + // (A) + + if (startIndexStack.empty()) { + break; // end traversal + } + + startIndex = startIndexStack.top(); + startIndexStack.pop(); + + endIndex = endIndexStack.top(); + endIndexStack.pop(); + + parentNodeIndex = parentNodeIndexStack.top(); + parentNodeIndexStack.pop(); + + nIter++; + } + + if (nIter >= maxIter) { + PUSH_ERROR_AND_RETURN("PathIndex tree Too deep."); + } + + return true; +} +#else +// TODO(syoyo): Refactor. Code is mostly identical to BuildDecompressedPathsImpl +bool CrateReader::BuildNodeHierarchy( + std::vector const &pathIndexes, + std::vector const &elementTokenIndexes, + std::vector const &jumps, + std::vector &visit_table, /* inout */ + size_t curIndex, + int64_t parentNodeIndex) { + bool hasChild = false, hasSibling = false; + + // NOTE: Need to indirectly lookup index through pathIndexes[] when accessing + // `_nodes` + do { + auto thisIndex = curIndex++; + DCOUT("thisIndex = " << thisIndex << ", curIndex = " << curIndex); + if (parentNodeIndex == -1) { + // root node. + // Assume single root node in the scene. + //assert(thisIndex == 0); + if (thisIndex != 0) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "TODO: Multiple root nodes."); + } + + if (thisIndex >= pathIndexes.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Index out-of-range."); + } + + size_t pathIdx = pathIndexes[thisIndex]; + if (pathIdx >= _paths.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "PathIndex out-of-range."); + } + + if (pathIdx >= _nodes.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "PathIndex out-of-range."); + } + + if (pathIdx >= visit_table.size()) { + // This should not be happan though + PUSH_ERROR_AND_RETURN_TAG(kTag, "[InternalError] out-of-range."); + } + + if (visit_table[pathIdx]) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Circular referencing detected. Invalid Prim tree representation."); + } + + Node root(parentNodeIndex, _paths[pathIdx]); + + _nodes[pathIdx] = root; + visit_table[pathIdx] = true; + + parentNodeIndex = int64_t(thisIndex); + + } else { + //if (parentNodeIndex >= int64_t(_nodes.size())) { + // PUSH_ERROR_AND_RETURN_TAG(kTag, "Parent Index out-of-range."); + //} + + if (parentNodeIndex >= int64_t(pathIndexes.size())) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Parent Index out-of-range."); + } + + if (thisIndex >= pathIndexes.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Index out-of-range."); + } + + DCOUT("Hierarchy. parent[" << pathIndexes[size_t(parentNodeIndex)] + << "].add_child = " << pathIndexes[thisIndex]); + + size_t pathIdx = pathIndexes[thisIndex]; + if (pathIdx >= _paths.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "PathIndex out-of-range."); + } + + if (pathIdx >= _nodes.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "PathIndex out-of-range."); + } + + if (pathIdx >= visit_table.size()) { + // This should not be happan though + PUSH_ERROR_AND_RETURN_TAG(kTag, "[InternalError] out-of-range."); + } + + if (visit_table[pathIdx]) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Circular referencing detected. Invalid Prim tree representation."); + } + + Node node(parentNodeIndex, _paths[pathIdx]); + + // Ensure parent is not set yet. + if (_nodes[pathIdx].GetParent() != -2) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "???: Maybe corrupted path hierarchy?."); + } + + _nodes[pathIdx] = node; + visit_table[pathIdx] = true; + + if (pathIdx >= _elemPaths.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "PathIndex out-of-range."); + } + + //std::string name = _paths[pathIndexes[thisIndex]].local_path_name(); + std::string name = _elemPaths[pathIdx].full_path_name(); + DCOUT("childName = " << name); + + size_t parentNodeIdx = size_t(parentNodeIndex); + if (parentNodeIdx >= pathIndexes.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "ParentNodeIdx out-of-range."); + } + + size_t parentPathIdx = pathIndexes[parentNodeIdx]; + if (parentPathIdx >= _nodes.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "PathIndex out-of-range."); + } + + if (!_nodes[parentPathIdx].AddChildren( + name, pathIdx)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid path index."); + } + } + + if (thisIndex >= jumps.size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Index is out-of-range"); + } + + hasChild = (jumps[thisIndex] > 0) || (jumps[thisIndex] == -1); + hasSibling = (jumps[thisIndex] >= 0); + + if (hasChild) { + if (hasSibling) { + auto siblingIndex = thisIndex + size_t(jumps[thisIndex]); + if (!BuildNodeHierarchy(pathIndexes, elementTokenIndexes, jumps, visit_table, + siblingIndex, parentNodeIndex)) { + return false; + } + } + // Have a child (may have also had a sibling). Reset parent node index + parentNodeIndex = int64_t(thisIndex); + DCOUT("parentNodeIndex = " << parentNodeIndex); + } + // If we had only a sibling, we just continue since the parent path is + // unchanged and the next thing in the reader stream is the sibling's + // header. + } while (hasChild || hasSibling); + + return true; +} +#endif + +bool CrateReader::ReadCompressedPaths(const uint64_t maxNumPaths) { + std::vector pathIndexes; + std::vector elementTokenIndexes; + std::vector jumps; + + // Read number of encoded paths. + uint64_t numEncodedPaths; + if (!_sr->read8(&numEncodedPaths)) { + _err += "Failed to read the number of encoded paths.\n"; + return false; + } + + DCOUT("maxNumPaths : " << maxNumPaths); + DCOUT("numEncodedPaths : " << numEncodedPaths); + + // Number of compressed paths could be less than maxNumPaths, + // but should not be greater. + if (maxNumPaths < numEncodedPaths) { + _err += "Size mismatch of numEncodedPaths at `PATHS` section.\n"; + return false; + } + + + // 3 = pathIndex, elementTokenIndex, jump + CHECK_MEMORY_USAGE(size_t(numEncodedPaths) * sizeof(int32_t) * 3); + + pathIndexes.resize(static_cast(numEncodedPaths)); + elementTokenIndexes.resize(static_cast(numEncodedPaths)); + jumps.resize(static_cast(numEncodedPaths)); + + size_t compBufferSize = Usd_IntegerCompression::GetCompressedBufferSize(static_cast(numEncodedPaths)); + size_t workspaceBufferSize = Usd_IntegerCompression::GetDecompressionWorkingSpaceSize(static_cast(numEncodedPaths)); + CHECK_MEMORY_USAGE(compBufferSize); + CHECK_MEMORY_USAGE(workspaceBufferSize); + + // Create temporary space for decompressing. + std::vector compBuffer(compBufferSize); + std::vector workingSpace(workspaceBufferSize); + + // pathIndexes. + { + uint64_t compPathIndexesSize; + if (!_sr->read8(&compPathIndexesSize)) { + _err += "Failed to read pathIndexesSize.\n"; + return false; + } + + if (compPathIndexesSize > compBufferSize) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid Compressed PathIndexes size."); + } + + CHECK_MEMORY_USAGE(size_t(compPathIndexesSize)); + + if (compPathIndexesSize != + _sr->read(size_t(compPathIndexesSize), size_t(compPathIndexesSize), + reinterpret_cast(compBuffer.data()))) { + _err += "Failed to read compressed pathIndexes data.\n"; + return false; + } + + DCOUT("comBuffer.size = " << compBuffer.size()); + DCOUT("compPathIndexesSize = " << compPathIndexesSize); + + std::string err; + Usd_IntegerCompression::DecompressFromBuffer( + compBuffer.data(), size_t(compPathIndexesSize), pathIndexes.data(), + size_t(numEncodedPaths), &err, workingSpace.data()); + if (!err.empty()) { + _err += "Failed to decode pathIndexes\n" + err; + return false; + } + } + + // elementTokenIndexes. + { + uint64_t compElementTokenIndexesSize; + if (!_sr->read8(&compElementTokenIndexesSize)) { + _err += "Failed to read elementTokenIndexesSize.\n"; + return false; + } + + if (compElementTokenIndexesSize > compBufferSize) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid Compressed elementTokenIndexes size."); + } + + CHECK_MEMORY_USAGE(size_t(compElementTokenIndexesSize)); + + if (compElementTokenIndexesSize != + _sr->read(size_t(compElementTokenIndexesSize), + size_t(compElementTokenIndexesSize), + reinterpret_cast(compBuffer.data()))) { + PUSH_ERROR("Failed to read elementTokenIndexes data."); + return false; + } + + std::string err; + Usd_IntegerCompression::DecompressFromBuffer( + compBuffer.data(), size_t(compElementTokenIndexesSize), + elementTokenIndexes.data(), size_t(numEncodedPaths), &err, + workingSpace.data()); + + if (!err.empty()) { + PUSH_ERROR("Failed to decode elementTokenIndexes."); + return false; + } + } + + // jumps. + { + uint64_t compJumpsSize; + if (!_sr->read8(&compJumpsSize)) { + PUSH_ERROR("Failed to read compressed jumpsSize."); + return false; + } + + if (compJumpsSize > compBufferSize) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid Compressed elementTokenIndexes size."); + } + + CHECK_MEMORY_USAGE(size_t(compJumpsSize)); + + if (compJumpsSize != + _sr->read(size_t(compJumpsSize), size_t(compJumpsSize), + reinterpret_cast(compBuffer.data()))) { + PUSH_ERROR("Failed to read compressed jumps data."); + return false; + } + + std::string err; + Usd_IntegerCompression::DecompressFromBuffer( + compBuffer.data(), size_t(compJumpsSize), jumps.data(), size_t(numEncodedPaths), + &err, workingSpace.data()); + + if (!err.empty()) { + PUSH_ERROR("Failed to decode jumps."); + return false; + } + } + +#ifdef TINYUSDZ_LOCAL_DEBUG_PRINT + for (size_t i = 0; i < pathIndexes.size(); i++) { + DCOUT("pathIndexes[" << i << "] = " << pathIndexes[i]); + } + + for (size_t i = 0; i < elementTokenIndexes.size(); i++) { + std::stringstream ss; + ss << "elementTokenIndexes[" << i << "] = " << elementTokenIndexes[i]; + int32_t tokIdx = elementTokenIndexes[i]; + if (tokIdx < 0) { + // Property Path. Need to negate it. + tokIdx = -tokIdx; + } + if (auto tokv = GetToken(crate::Index(uint32_t(tokIdx)))) { + ss << "(" << tokv.value() << ")"; + } + ss << "\n"; + DCOUT(ss.str()); + } + + for (size_t i = 0; i < jumps.size(); i++) { + DCOUT(fmt::format("jumps[{}] = {}", i, jumps[i])); + } +#endif + + // For circular tree check + std::vector visit_table; + CHECK_MEMORY_USAGE(_paths.size()); // TODO: divide by 8? + + // `_paths` is already initialized just before calling this ReadCompressedPaths + visit_table.resize(_paths.size()); + for (size_t i = 0; i < visit_table.size(); i++) { + visit_table[i] = false; + } + + // Now build the paths. +#if defined(TINYUSDZ_CRATE_USE_FOR_BASED_PATH_INDEX_DECODER) + BuildDecompressedPathsArg arg; + arg.pathIndexes = &pathIndexes; + arg.elementTokenIndexes = &elementTokenIndexes; + arg.jumps = &jumps; + arg.visit_table = &visit_table; + arg.startIndex = 0; + arg.endIndex = pathIndexes.size() - 1; // or numEncodedPaths - 1 + arg.parentPath = Path(); + if (!BuildDecompressedPathsImpl(&arg)) { + return false; + } + +#else + if (!BuildDecompressedPathsImpl(pathIndexes, elementTokenIndexes, jumps, visit_table, + /* curIndex */ 0, Path())) { + return false; + } +#endif + + // + // Ensure decoded numEncodedPaths. + // + size_t sumDecodedPaths = 0; + for (size_t i = 0; i < visit_table.size(); i++) { + if (visit_table[i]) { + sumDecodedPaths++; + } + } + if (sumDecodedPaths != numEncodedPaths) { + PUSH_ERROR_AND_RETURN(fmt::format("Decoded {} paths but numEncodedPaths in Crate is {}. Possible corruption of Crate data.", + sumDecodedPaths, numEncodedPaths)); + } + + // Now build node hierarchy. + + // Circular referencing check should be done in BuildDecompressedPathsImpl, + // but do check it again just in case. + for (size_t i = 0; i < visit_table.size(); i++) { + visit_table[i] = false; + } + if (!BuildNodeHierarchy(pathIndexes, elementTokenIndexes, jumps, visit_table, + /* curIndex */ 0, /* parent node index */ -1)) { + return false; + } + + sumDecodedPaths = 0; + for (size_t i = 0; i < visit_table.size(); i++) { + if (visit_table[i]) { + sumDecodedPaths++; + } + } + if (sumDecodedPaths != numEncodedPaths) { + PUSH_ERROR_AND_RETURN(fmt::format("Decoded {} paths but numEncodedPaths in BuildNodeHierarchy is {}. Possible corruption of Crate data.", + sumDecodedPaths, numEncodedPaths)); + } + + return true; +} + +bool CrateReader::ReadSection(crate::Section *s) { + size_t name_len = crate::kSectionNameMaxLength + 1; + + if (name_len != + _sr->read(name_len, name_len, reinterpret_cast(s->name))) { + _err += "Failed to read section.name.\n"; + return false; + } + + if (!_sr->read8(&s->start)) { + _err += "Failed to read section.start.\n"; + return false; + } + + if (size_t(s->start) > _sr->size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Section start offset exceeds USDC file size."); + } + + if (!_sr->read8(&s->size)) { + _err += "Failed to read section.size.\n"; + return false; + } + + if (size_t(s->start + s->size) > _sr->size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Section end offset exceeds USDC file size."); + } + + + return true; +} + +bool CrateReader::ReadTokens() { + if ((_tokens_index < 0) || (_tokens_index >= int64_t(_toc.sections.size()))) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid index for `TOKENS` section."); + } + + if ((_version[0] == 0) && (_version[1] < 4)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Version must be 0.4.0 or later, but got {}.{}.{}", + _version[0], _version[1], _version[2])); + } + + const crate::Section &sec = _toc.sections[size_t(_tokens_index)]; + if (!_sr->seek_set(uint64_t(sec.start))) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to move to `TOKENS` section."); + return false; + } + + if (sec.size < 4) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("`TOKENS` section data size is zero or too small.")); + } + + DCOUT("sec.start = " << sec.start); + DCOUT("sec.size = " << sec.size); + + // # of tokens. + uint64_t num_tokens; + if (!_sr->read8(&num_tokens)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read # of tokens at `TOKENS` section."); + } + + DCOUT("# of tokens = " << num_tokens); + + if (num_tokens == 0) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Empty tokens."); + } + + if (num_tokens > _config.maxNumTokens) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Too many Tokens."); + } + + // Tokens are lz4 compressed starting from version 0.4.0 + + // Compressed token data. + uint64_t uncompressedSize; + if (!_sr->read8(&uncompressedSize)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read uncompressedSize at `TOKENS` section."); + } + + DCOUT("uncompressedSize = " << uncompressedSize); + + + // Must be larger than len(';-)') + all empty string case. + // 3 = ';-)' + // num_tokens = '\0' delimiter + if ((3 + num_tokens) > uncompressedSize) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "`TOKENS` section corrupted."); + } + + // At least min size should be 16 both for compress and uncompress. + if (uncompressedSize < 4) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "uncompressedSize too small or zero bytes."); + } + + uint64_t compressedSize; + if (!_sr->read8(&compressedSize)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read compressedSize at `TOKENS` section."); + } + + DCOUT("compressedSize = " << compressedSize); + + if (compressedSize < 4) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "compressedSize is too small or zero bytes."); + } + + if (compressedSize > _sr->size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Compressed data size exceeds input file size."); + } + + if (size_t(compressedSize) > size_t(sec.size)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Compressed data size exceeds `TOKENS` section size."); + } + + // To combat with heap-buffer flow in lz4 cuased by corrupted lz4 compressed data, + // We allocate same size of uncompressedSize(or larger one), + // And further, extra 128 bytes for safety(LZ4_FAST_DEC_LOOP does 16 bytes stride memcpy) + + uint64_t bufSize = (std::max)(compressedSize, uncompressedSize); + CHECK_MEMORY_USAGE(bufSize+128); + CHECK_MEMORY_USAGE(uncompressedSize); + + + // dst + std::vector chars(static_cast(uncompressedSize)); + memset(chars.data(), 0, chars.size()); + + std::vector compressed(static_cast(bufSize + 128)); + memset(compressed.data(), 0, compressed.size()); + + if (compressedSize != + _sr->read(size_t(compressedSize), size_t(compressedSize), + reinterpret_cast(compressed.data()))) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read compressed data at `TOKENS` section."); + return false; + } + + if (uncompressedSize != + LZ4Compression::DecompressFromBuffer(compressed.data(), chars.data(), + size_t(compressedSize), + size_t(uncompressedSize), &_err)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to decompress data of Tokens."); + } + + // Split null terminated string into _tokens. + const char *ps = chars.data(); + const char *pe = chars.data() + chars.size(); + const char *pcurr = ps; + size_t nbytes_remain = size_t(chars.size()); + + auto my_strnlen = [](const char *s, const size_t max_length) -> size_t { + if (!s) return 0; + + size_t i = 0; + for (; i < max_length; i++) { + if (s[i] == '\0') { + return i; + } + } + + // null character not found. + return i; + }; + + // TODO(syoyo): Check if input string has exactly `n` tokens(`n` null + // characters) + for (size_t i = 0; i < num_tokens; i++) { + DCOUT("n_remain = " << nbytes_remain); + + size_t len = my_strnlen(pcurr, nbytes_remain); + DCOUT("len = " << len); + + if ((pcurr + (len+1)) > pe) { + _err += "Invalid token string array.\n"; + return false; + } + + std::string str; + if (len > 0) { + str = std::string(pcurr, len); + } else { + // Empty string allowed + str = std::string(); + } + + pcurr += len + 1; // +1 = '\0' + nbytes_remain = size_t(pe - pcurr); + if (pcurr > pe) { + _err += "Invalid token string array.\n"; + return false; + } + + value::token tok(str); + + DCOUT("token[" << i << "] = " << tok); + _tokens.push_back(tok); + + if (nbytes_remain == 0) { + // reached to the string buffer end. + break; + } + } + + if (_tokens.size() != num_tokens) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("The number of tokens parsed {} does not match the requested one {}", _tokens.size(), num_tokens)); + } + + return true; +} + +bool CrateReader::ReadStrings() { + if ((_strings_index < 0) || + (_strings_index >= int64_t(_toc.sections.size()))) { + _err += "Invalid index for `STRINGS` section.\n"; + return false; + } + + const crate::Section &s = _toc.sections[size_t(_strings_index)]; + + if (s.size == 0) { + // empty `STRINGS`? + return true; + } + + if (!_sr->seek_set(uint64_t(s.start))) { + _err += "Failed to move to `STRINGS` section.\n"; + return false; + } + + // `STRINGS` are not compressed. + if (!ReadIndices(&_string_indices)) { + _err += "Failed to read StringIndex array.\n"; + return false; + } + + for (size_t i = 0; i < _string_indices.size(); i++) { + DCOUT("StringIndex[" << i << "] = " << _string_indices[i].value); + } + + return true; +} + +bool CrateReader::ReadFields() { + if ((_fields_index < 0) || (_fields_index >= int64_t(_toc.sections.size()))) { + _err += "Invalid index for `FIELDS` section.\n"; + return false; + } + + if ((_version[0] == 0) && (_version[1] < 4)) { + _err += "Version must be 0.4.0 or later, but got " + + std::to_string(_version[0]) + "." + std::to_string(_version[1]) + + "." + std::to_string(_version[2]) + "\n"; + return false; + } + + const crate::Section &s = _toc.sections[size_t(_fields_index)]; + + if (s.size == 0) { + // accepts Empty FIELDS size. + return true; + } + + if (!_sr->seek_set(uint64_t(s.start))) { + _err += "Failed to move to `FIELDS` section.\n"; + return false; + } + + uint64_t num_fields; + if (!_sr->read8(&num_fields)) { + _err += "Failed to read # of fields at `FIELDS` section.\n"; + return false; + } + + DCOUT("num_fields = " << num_fields); + + if (num_fields == 0) { + // Fields may be empty, so OK + return true; + } + + if (num_fields > _config.maxNumFields) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Too many fields in `FIELDS` section."); + } + + if (sizeof(void *) == 4) { + // 32bit + if (num_fields > std::numeric_limits::max() / sizeof(uint32_t)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Too many fields in `FIELDS` section."); + } + } + + CHECK_MEMORY_USAGE(size_t(num_fields) * sizeof(Field)); + + _fields.resize(static_cast(num_fields)); + + // indices + { + + CHECK_MEMORY_USAGE(size_t(num_fields) * sizeof(uint32_t)); + + std::vector tmp; + tmp.resize(size_t(num_fields)); + if (!ReadCompressedInts(tmp.data(), size_t(num_fields))) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read Field token_index array."); + } + + for (size_t i = 0; i < num_fields; i++) { + _fields[i].token_index.value = tmp[i]; + } + + REDUCE_MEMORY_USAGE(size_t(num_fields) * sizeof(uint32_t)); + + } + + // Value reps(LZ4 compressed) + { + uint64_t reps_size; // compressed size + if (!_sr->read8(&reps_size)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read value reps legnth at `FIELDS` section."); + } + + if (reps_size > size_t(s.size)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid byte size of Value reps data."); + } + + if (reps_size > _sr->size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Compressed Value reps size exceeds USDC data."); + } + + CHECK_MEMORY_USAGE(size_t(reps_size)); + + // TODO: Decompress from _sr directly. + std::vector comp_buffer(static_cast(reps_size)); + + if (reps_size != + _sr->read(size_t(reps_size), size_t(reps_size), + reinterpret_cast(comp_buffer.data()))) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read reps data at `FIELDS` section."); + } + + // reps datasize = LZ4 compressed. uncompressed size = num_fields * 8 bytes + size_t uncompressed_size = size_t(num_fields) * sizeof(uint64_t); + CHECK_MEMORY_USAGE(uncompressed_size); + + std::vector reps_data; + reps_data.resize(static_cast(num_fields)); + + + if (uncompressed_size != LZ4Compression::DecompressFromBuffer( + comp_buffer.data(), + reinterpret_cast(reps_data.data()), + size_t(reps_size), uncompressed_size, &_err)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read Fields ValueRep data."); + } + + for (size_t i = 0; i < num_fields; i++) { + _fields[i].value_rep = crate::ValueRep(reps_data[i]); + } + + REDUCE_MEMORY_USAGE(uncompressed_size); + REDUCE_MEMORY_USAGE(size_t(reps_size)); // comp_buffer + } + + DCOUT("num_fields = " << num_fields); + for (size_t i = 0; i < num_fields; i++) { + if (auto tokv = GetToken(_fields[i].token_index)) { + DCOUT("field[" << i << "] name = " << tokv.value() + << ", value = " << _fields[i].value_rep.GetStringRepr()); + } + } + + return true; +} + +bool CrateReader::ReadFieldSets() { + if ((_fieldsets_index < 0) || + (_fieldsets_index >= int64_t(_toc.sections.size()))) { + _err += "Invalid index for `FIELDSETS` section.\n"; + return false; + } + + if ((_version[0] == 0) && (_version[1] < 4)) { + _err += "Version must be 0.4.0 or later, but got " + + std::to_string(_version[0]) + "." + std::to_string(_version[1]) + + "." + std::to_string(_version[2]) + "\n"; + return false; + } + + const crate::Section &s = _toc.sections[size_t(_fieldsets_index)]; + + if (!_sr->seek_set(uint64_t(s.start))) { + _err += "Failed to move to `FIELDSETS` section.\n"; + return false; + } + + uint64_t num_fieldsets; + if (!_sr->read8(&num_fieldsets)) { + _err += "Failed to read # of fieldsets at `FIELDSETS` section.\n"; + return false; + } + + if (num_fieldsets == 0) { + // At least 1 FieldIndex(separator(~0)) must exist. + PUSH_ERROR("`FIELDSETS` is empty."); + return false; + } + + if (num_fieldsets > _config.maxNumFieldSets) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Too many FieldSets {}. maxNumFieldSets is set to {}", num_fieldsets, _config.maxNumFieldSets)); + } + + CHECK_MEMORY_USAGE(size_t(num_fieldsets) * sizeof(uint32_t)); + + _fieldset_indices.resize(static_cast(num_fieldsets)); + + // Create temporary space for decompressing. + size_t compBufferSize = Usd_IntegerCompression::GetCompressedBufferSize( + static_cast(num_fieldsets)); + + CHECK_MEMORY_USAGE(compBufferSize); + + std::vector comp_buffer; + comp_buffer.resize(compBufferSize); + + CHECK_MEMORY_USAGE(sizeof(uint32_t) * size_t(num_fieldsets)); + std::vector tmp; + tmp.resize(static_cast(num_fieldsets)); + + size_t workBufferSize = Usd_IntegerCompression::GetDecompressionWorkingSpaceSize( + static_cast(num_fieldsets)); + + CHECK_MEMORY_USAGE(workBufferSize); + std::vector working_space; + working_space.resize(workBufferSize); + + uint64_t fsets_size; + if (!_sr->read8(&fsets_size)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read fieldsets size at `FIELDSETS` section."); + } + + DCOUT("num_fieldsets = " << num_fieldsets << ", fsets_size = " << fsets_size + << ", comp_buffer.size = " << comp_buffer.size()); + + if (fsets_size > comp_buffer.size()) { + // Maybe corrupted? + fsets_size = comp_buffer.size(); + } + + if (fsets_size > _sr->size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "FieldSets compressed data exceeds USDC data."); + } + + if (fsets_size != + _sr->read(size_t(fsets_size), size_t(fsets_size), + reinterpret_cast(comp_buffer.data()))) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to read fieldsets data at `FIELDSETS` section."); + } + + std::string err; + Usd_IntegerCompression::DecompressFromBuffer( + comp_buffer.data(), size_t(fsets_size), tmp.data(), size_t(num_fieldsets), + &err, working_space.data()); + + if (!err.empty()) { + _err += err; + return false; + } + + for (size_t i = 0; i != num_fieldsets; ++i) { + DCOUT("fieldset_index[" << i << "] = " << tmp[i]); + _fieldset_indices[i].value = tmp[i]; + } + + REDUCE_MEMORY_USAGE(workBufferSize); + REDUCE_MEMORY_USAGE(compBufferSize); + + return true; +} + +bool CrateReader::BuildLiveFieldSets() { + for (auto fsBegin = _fieldset_indices.begin(), + fsEnd = std::find(fsBegin, _fieldset_indices.end(), crate::Index()); + fsBegin != _fieldset_indices.end(); + fsBegin = fsEnd + 1, fsEnd = std::find(fsBegin, _fieldset_indices.end(), + crate::Index())) { + auto &pairs = _live_fieldsets[crate::Index( + uint32_t(fsBegin - _fieldset_indices.begin()))]; + + pairs.resize(size_t(fsEnd - fsBegin)); + DCOUT("range size = " << (fsEnd - fsBegin)); + // TODO(syoyo): Parallelize. + for (size_t i = 0; fsBegin != fsEnd; ++fsBegin, ++i) { + if (fsBegin->value < _fields.size()) { + // ok + } else { + PUSH_ERROR("Invalid live field set data."); + return false; + } + + DCOUT("fieldIndex = " << (fsBegin->value)); + auto const &field = _fields[fsBegin->value]; + if (auto tokv = GetToken(field.token_index)) { + pairs[i].first = tokv.value().str(); + + if (!UnpackValueRep(field.value_rep, &pairs[i].second)) { + PUSH_ERROR("BuildLiveFieldSets: Failed to unpack ValueRep : " + << field.value_rep.GetStringRepr()); + return false; + } + } else { + PUSH_ERROR("Invalid token index."); + } + } + } + + DCOUT("# of live fieldsets = " << _live_fieldsets.size()); + +#ifdef TINYUSDZ_LOCAL_DEBUG_PRINT + size_t sum = 0; + for (const auto &item : _live_fieldsets) { + DCOUT("livefieldsets[" << item.first.value + << "].count = " << item.second.size()); + sum += item.second.size(); + + for (size_t i = 0; i < item.second.size(); i++) { + DCOUT(" [" << i << "] name = " << item.second[i].first); + } + } + DCOUT("Total fields used = " << sum); +#endif + + return true; +} + +bool CrateReader::ReadSpecs() { + if ((_specs_index < 0) || (_specs_index >= int64_t(_toc.sections.size()))) { + PUSH_ERROR("Invalid index for `SPECS` section."); + return false; + } + + if ((_version[0] == 0) && (_version[1] < 4)) { + PUSH_ERROR("Version must be 0.4.0 or later, but got " + + std::to_string(_version[0]) + "." + std::to_string(_version[1]) + + "." + std::to_string(_version[2])); + return false; + } + + const crate::Section &s = _toc.sections[size_t(_specs_index)]; + + if (!_sr->seek_set(uint64_t(s.start))) { + PUSH_ERROR("Failed to move to `SPECS` section."); + return false; + } + + uint64_t num_specs; + if (!_sr->read8(&num_specs)) { + PUSH_ERROR("Failed to read # of specs size at `SPECS` section."); + return false; + } + + if (num_specs > _config.maxNumSpecifiers) { + PUSH_ERROR("Too many specs in `SPECS` section."); + return false; + } + + if (num_specs == 0) { + // At least 1 Spec(Root Prim '/') must exist. + PUSH_ERROR("`SPECS` is empty."); + return false; + } + + DCOUT("num_specs " << num_specs); + + CHECK_MEMORY_USAGE(size_t(num_specs) * sizeof(Spec)); + + _specs.resize(static_cast(num_specs)); + + // TODO: Memory size check + + // Create temporary space for decompressing. + size_t compBufferSize= Usd_IntegerCompression::GetCompressedBufferSize( + static_cast(num_specs)); + + CHECK_MEMORY_USAGE(compBufferSize); + + std::vector comp_buffer; + comp_buffer.resize(compBufferSize); + + CHECK_MEMORY_USAGE(size_t(num_specs) * sizeof(uint32_t)); // tmp + + std::vector tmp(static_cast(num_specs)); + + size_t workBufferSize= Usd_IntegerCompression::GetDecompressionWorkingSpaceSize( + static_cast(num_specs)); + + CHECK_MEMORY_USAGE(workBufferSize); + std::vector working_space; + working_space.resize(workBufferSize); + + // path indices + { + uint64_t path_indexes_size; + if (!_sr->read8(&path_indexes_size)) { + PUSH_ERROR("Failed to read path indexes size at `SPECS` section."); + return false; + } + + if (path_indexes_size > comp_buffer.size()) { + // Maybe corrupted? + path_indexes_size = comp_buffer.size(); + } + + if (path_indexes_size != + _sr->read(size_t(path_indexes_size), size_t(path_indexes_size), + reinterpret_cast(comp_buffer.data()))) { + PUSH_ERROR("Failed to read path indexes data at `SPECS` section."); + return false; + } + + std::string err; // not used + if (!Usd_IntegerCompression::DecompressFromBuffer( + comp_buffer.data(), size_t(path_indexes_size), tmp.data(), + size_t(num_specs), &err, working_space.data())) { + PUSH_ERROR("Failed to decode pathIndexes at `SPECS` section."); + return false; + } + + for (size_t i = 0; i < num_specs; ++i) { + DCOUT("spec[" << i << "].path_index = " << tmp[i]); + _specs[i].path_index.value = tmp[i]; + } + } + + // fieldset indices + { + uint64_t fset_indexes_size; + if (!_sr->read8(&fset_indexes_size)) { + PUSH_ERROR("Failed to read fieldset indexes size at `SPECS` section."); + return false; + } + + if (fset_indexes_size > comp_buffer.size()) { + // Maybe corrupted? + fset_indexes_size = comp_buffer.size(); + } + + if (fset_indexes_size != + _sr->read(size_t(fset_indexes_size), size_t(fset_indexes_size), + reinterpret_cast(comp_buffer.data()))) { + PUSH_ERROR("Failed to read fieldset indexes data at `SPECS` section."); + return false; + } + + std::string err; // not used + if (!Usd_IntegerCompression::DecompressFromBuffer( + comp_buffer.data(), size_t(fset_indexes_size), tmp.data(), + size_t(num_specs), &err, working_space.data())) { + PUSH_ERROR("Failed to decode fieldset indices at `SPECS` section."); + return false; + } + + for (size_t i = 0; i != num_specs; ++i) { + DCOUT("specs[" << i << "].fieldset_index = " << tmp[i]); + _specs[i].fieldset_index.value = tmp[i]; + } + } + + // spec types + { + uint64_t spectype_size; + if (!_sr->read8(&spectype_size)) { + PUSH_ERROR("Failed to read spectype size at `SPECS` section."); + return false; + } + + if (spectype_size > comp_buffer.size()) { + // Maybe corrupted? + spectype_size = comp_buffer.size(); + } + + if (spectype_size != + _sr->read(size_t(spectype_size), size_t(spectype_size), + reinterpret_cast(comp_buffer.data()))) { + PUSH_ERROR("Failed to read spectype data at `SPECS` section."); + return false; + } + + std::string err; // not used. + if (!Usd_IntegerCompression::DecompressFromBuffer( + comp_buffer.data(), size_t(spectype_size), tmp.data(), + size_t(num_specs), &err, working_space.data())) { + PUSH_ERROR("Failed to decode fieldset indices at `SPECS` section.\n"); + return false; + } + + for (size_t i = 0; i != num_specs; ++i) { + // std::cout << "spectype = " << tmp[i] << "\n"; + _specs[i].spec_type = static_cast(tmp[i]); + } + } + +#ifdef TINYUSDZ_LOCAL_DEBUG_PRINT + for (size_t i = 0; i != num_specs; ++i) { + DCOUT("spec[" << i << "].pathIndex = " << _specs[i].path_index.value + << ", fieldset_index = " << _specs[i].fieldset_index.value + << ", spec_type = " + << tinyusdz::to_string(_specs[i].spec_type)); + if (auto specstr = GetSpecString(crate::Index(uint32_t(i)))) { + DCOUT("spec[" << i << "] string_repr = " << specstr.value()); + } + } +#endif + + REDUCE_MEMORY_USAGE(compBufferSize); + REDUCE_MEMORY_USAGE(workBufferSize); + REDUCE_MEMORY_USAGE(size_t(num_specs) * sizeof(uint32_t)); // tmp + + return true; +} + +bool CrateReader::ReadPaths() { + if ((_paths_index < 0) || (_paths_index >= int64_t(_toc.sections.size()))) { + PUSH_ERROR("Invalid index for `PATHS` section."); + return false; + } + + if ((_version[0] == 0) && (_version[1] < 4)) { + PUSH_ERROR("Version must be 0.4.0 or later, but got " + + std::to_string(_version[0]) + "." + std::to_string(_version[1]) + + "." + std::to_string(_version[2])); + return false; + } + + const crate::Section &s = _toc.sections[size_t(_paths_index)]; + + if (!_sr->seek_set(uint64_t(s.start))) { + PUSH_ERROR("Failed to move to `PATHS` section."); + return false; + } + + uint64_t num_paths; + if (!_sr->read8(&num_paths)) { + PUSH_ERROR("Failed to read # of paths at `PATHS` section."); + return false; + } + + if (num_paths == 0) { + // At least root path exits. + PUSH_ERROR_AND_RETURN_TAG(kTag, "`PATHS` is empty."); + } + + if (num_paths > _config.maxNumPaths) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Too many Paths in `PATHS` section."); + } + + CHECK_MEMORY_USAGE(size_t(num_paths) * sizeof(Path)); // conservative estimation + CHECK_MEMORY_USAGE(size_t(num_paths) * sizeof(Path)); // conservative estimation + CHECK_MEMORY_USAGE(size_t(num_paths) * sizeof(Node)); // conservative estimation + + _paths.resize(static_cast(num_paths)); + _elemPaths.resize(static_cast(num_paths)); + _nodes.resize(static_cast(num_paths)); + + if (!ReadCompressedPaths(num_paths)) { + PUSH_ERROR("Failed to read compressed paths."); + return false; + } + +#ifdef TINYUSDZ_LOCAL_DEBUG_PRINT + DCOUT("# of paths " << _paths.size()); + + for (size_t i = 0; i < _paths.size(); i++) { + DCOUT("path[" << i << "] = " << _paths[i].full_path_name()); + } +#endif + + return true; +} + +bool CrateReader::ReadBootStrap() { + // parse header. + uint8_t magic[8]; + if (8 != _sr->read(/* req */ 8, /* dst len */ 8, magic)) { + PUSH_ERROR("Failed to read magic number."); + return false; + } + + if (memcmp(magic, "PXR-USDC", 8)) { + PUSH_ERROR("Invalid magic number. Expected 'PXR-USDC' but got '" + + std::string(magic, magic + 8) + "'"); + return false; + } + + // parse version(first 3 bytes from 8 bytes) + uint8_t version[8]; + if (8 != _sr->read(8, 8, version)) { + PUSH_ERROR("Failed to read magic number."); + return false; + } + + DCOUT("version = " << int(version[0]) << "." << int(version[1]) << "." + << int(version[2])); + + _version[0] = version[0]; + _version[1] = version[1]; + _version[2] = version[2]; + + // We only support version 0.4.0 or later. + if ((version[0] == 0) && (version[1] < 4)) { + PUSH_ERROR("Version must be 0.4.0 or later, but got " + + std::to_string(version[0]) + "." + std::to_string(version[1]) + + "." + std::to_string(version[2])); + return false; + } + + // Currently up to 0.9.0 + if ((version[0] == 0) && (version[1] < 10)) { + // ok + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Unsupported version {}.{}.{}. TinyUSDZ supports version up to 0.9.0", + _version[0], _version[1], _version[2])); + } + + _toc_offset = 0; + if (!_sr->read8(&_toc_offset)) { + PUSH_ERROR("Failed to read TOC offset."); + return false; + } + + if ((_toc_offset <= 88) || (_toc_offset >= int64_t(_sr->size()))) { + PUSH_ERROR("Invalid TOC offset value: " + std::to_string(_toc_offset) + + ", filesize = " + std::to_string(_sr->size()) + "."); + return false; + } + + DCOUT("toc offset = " << _toc_offset); + + return true; +} + +bool CrateReader::ReadTOC() { + + DCOUT(fmt::format("Memory budget: {} bytes", _config.maxMemoryBudget)); + + if ((_toc_offset <= 88) || (_toc_offset >= int64_t(_sr->size()))) { + PUSH_ERROR("Invalid toc offset."); + return false; + } + + if (!_sr->seek_set(uint64_t(_toc_offset))) { + PUSH_ERROR("Failed to move to TOC offset."); + return false; + } + + // read # of sections. + uint64_t num_sections{0}; + if (!_sr->read8(&num_sections)) { + PUSH_ERROR("Failed to read TOC(# of sections)."); + return false; + } + if (num_sections >= _config.maxTOCSections) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("# of sections {} are too large. maxTOCSections is set to {}", num_sections, _config.maxTOCSections)); + } + + DCOUT("toc sections = " << num_sections); + + _toc.sections.resize(static_cast(num_sections)); + + CHECK_MEMORY_USAGE(num_sections * sizeof(Section)); + + for (size_t i = 0; i < num_sections; i++) { + if (!ReadSection(&_toc.sections[i])) { + PUSH_ERROR("Failed to read TOC section at " + std::to_string(i)); + return false; + } + DCOUT("section[" << i << "] name = " << _toc.sections[i].name + << ", start = " << _toc.sections[i].start + << ", size = " << _toc.sections[i].size); + + if (_toc.sections[i].start < 0) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Invalid section start byte offset.")); + } + + if (_toc.sections[i].size <= 0) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Invalid or empty section size.")); + } + + if (size_t(_toc.sections[i].size) > _sr->size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Section size exceeds input USDC data size.")); + } + + if (size_t(_toc.sections[i].start) > _sr->size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Section start byte offset exceeds input USDC data size.")); + } + + // TODO: handle integer overflow. + size_t end_offset = size_t(_toc.sections[i].start + _toc.sections[i].size); + if (sizeof(void *) == 4) { // 32bit + if (end_offset > std::numeric_limits::max()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Section end offset exceeds 32bit max.")); + } + } + if (end_offset > _sr->size()) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Section byte offset + size exceeds input USDC data size.")); + } + + + if (0 == strncmp(_toc.sections[i].name, "TOKENS", + crate::kSectionNameMaxLength)) { + _tokens_index = int64_t(i); + } else if (0 == strncmp(_toc.sections[i].name, "STRINGS", + crate::kSectionNameMaxLength)) { + _strings_index = int64_t(i); + } else if (0 == strncmp(_toc.sections[i].name, "FIELDS", + crate::kSectionNameMaxLength)) { + _fields_index = int64_t(i); + } else if (0 == strncmp(_toc.sections[i].name, "FIELDSETS", + crate::kSectionNameMaxLength)) { + _fieldsets_index = int64_t(i); + } else if (0 == strncmp(_toc.sections[i].name, "SPECS", + crate::kSectionNameMaxLength)) { + _specs_index = int64_t(i); + } else if (0 == strncmp(_toc.sections[i].name, "PATHS", + crate::kSectionNameMaxLength)) { + _paths_index = int64_t(i); + } + } + + DCOUT("TOC read success"); + return true; +} + +/// +/// Find if a field with (`name`, `tyname`) exists in FieldValuePairVector. +/// +bool CrateReader::HasFieldValuePair(const FieldValuePairVector &fvs, + const std::string &name, + const std::string &tyname) { + for (const auto &fv : fvs) { + if ((fv.first == name) && (fv.second.type_name() == tyname)) { + return true; + } + } + + return false; +} + +/// +/// Find if a field with `name`(type can be arbitrary) exists in +/// FieldValuePairVector. +/// +bool CrateReader::HasFieldValuePair(const FieldValuePairVector &fvs, + const std::string &name) { + for (const auto &fv : fvs) { + if (fv.first == name) { + return true; + } + } + + return false; +} + +nonstd::expected +CrateReader::GetFieldValuePair(const FieldValuePairVector &fvs, + const std::string &name, + const std::string &tyname) { + for (const auto &fv : fvs) { + if ((fv.first == name) && (fv.second.type_name() == tyname)) { + return fv; + } + } + + return nonstd::make_unexpected("FieldValuePair not found with name: `" + + name + "` and specified type: `" + tyname + + "`"); +} + +nonstd::expected +CrateReader::GetFieldValuePair(const FieldValuePairVector &fvs, + const std::string &name) { + for (const auto &fv : fvs) { + if (fv.first == name) { + return fv; + } + } + + return nonstd::make_unexpected("FieldValuePair not found with name: `" + + name + "`"); +} + + +} // namespace crate +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/crate-reader.hh b/contrib/tinyusdz/tinyusdz_repo/src/crate-reader.hh new file mode 100644 index 000000000..50faf0671 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/crate-reader.hh @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +#pragma once + +#include +#include + +// +#include "nonstd/optional.hpp" +// +#include "crate-format.hh" +#include "prim-types.hh" +#include "stream-reader.hh" + +namespace tinyusdz { +namespace crate { + +// on: Use for-based PathIndex tree decoder to avoid potential buffer overflow(new implementation. its not well tested with fuzzer) +// off: Use recursive function call to decode PathIndex tree(its been working for a years and tested with fuzzer) +// TODO: After several battle-testing, make for-based PathIndex tree decoder default +#define TINYUSDZ_CRATE_USE_FOR_BASED_PATH_INDEX_DECODER + +struct CrateReaderConfig { + int numThreads = -1; + + // For malcious Crate data. + // Set limits to prevent infinite-loop, buffer-overrun, out-of-memory, etc. + size_t maxTOCSections = 32; + + size_t maxNumTokens = 1024 * 1024 * 64; + size_t maxNumStrings = 1024 * 1024 * 64; + size_t maxNumFields = 1024 * 1024 * 256; + size_t maxNumFieldSets = 1024 * 1024 * 256; + size_t maxNumSpecifiers = 1024 * 1024 * 256; + size_t maxNumPaths = 1024 * 1024 * 256; + + size_t maxNumIndices = 1024 * 1024 * 256; + size_t maxDictElements = 256; + size_t maxArrayElements = 1024 * 1024 * 1024; // 1G + size_t maxAssetPathElements = 512; + + size_t maxTokenLength = 4096; // Maximum allowed length of `token` string + size_t maxStringLength = 1024 * 1024 * 64; + + size_t maxVariantsMapElements = 128; + + size_t maxValueRecursion = 16; // Prevent recursive Value unpack(e.g. Value encodes itself) + size_t maxPathIndicesDecodeIteration = 1024 * 1024 * 256; // Prevent infinite loop BuildDecompressedPathsImpl + + // Generic int[] data + size_t maxInts = 1024 * 1024 * 1024; + + // Total memory budget for uncompressed USD data(vertices, `tokens`, ...)` in + // [bytes]. + size_t maxMemoryBudget = std::numeric_limits::max(); // Default 2GB +}; + +/// +/// Crate(binary data) reader +/// +class CrateReader { + public: + /// + /// Intermediate Node data structure for scene graph. + /// This does not contain actual prim/property data. + /// + class Node { + public: + // -2 = initialize as invalid node + Node() : _parent(-2) {} + + Node(int64_t parent, Path &path) : _parent(parent), _path(path) {} + + int64_t GetParent() const { return _parent; } + + const std::vector &GetChildren() const { return _children; } + + /// + /// child_name is used when reconstructing scene graph. + /// Return false when `child_name` is already added to a children. + /// + bool AddChildren(const std::string &child_name, size_t node_index) { + if (_primChildren.count(child_name)) { + return false; + } + // assert(_primChildren.count(child_name) == 0); + _primChildren.emplace(child_name); + _children.push_back(node_index); + return true; + } + + /// + /// Get full path(e.g. `/muda/dora/bora` when the parent is `/muda/dora` and + /// this node is `bora`) + /// + // std::string GetFullPath() const { return _path.full_path_name(); } + + /// + /// Get local path + /// + std::string GetLocalPath() const { return _path.full_path_name(); } + + /// + /// Element Path(= name of Prim. Tokens in `primChildren` field). Prim node + /// only. + /// + void SetElementPath(Path &path) { _elemPath = path; } + + nonstd::optional GetElementName() const { + if (_elemPath.is_relative_path()) { + return _elemPath.full_path_name(); + } else { + return nonstd::nullopt; + } + } + + // Element path(e.g. `geom0`) + const Path &GetElementPath() const { return _elemPath; } + + // Full path(e.g. `/root/geom0` + const Path &GetPath() const { return _path; } + + // crate::CrateDataType GetNodeDataType() const { return _node_type; } + + const std::unordered_set &GetPrimChildren() const { + return _primChildren; + } + + // void SetAssetInfo(const value::dict &dict) { _assetInfo = dict; } + // const value::dict &GetAssetInfo() const { return _assetInfo; } + + private: + int64_t + _parent; // -1 = this node is the root node. -2 = invalid or leaf node + std::vector _children; // index to child nodes. + std::unordered_set + _primChildren; // List of name of child nodes + + Path _path; // local path + // value::dict _assetInfo; + Path _elemPath; + + // value::TypeId _node_type; + // NodeType _node_type; + }; + + public: + private: + CrateReader() = delete; + + public: + CrateReader(StreamReader *sr, + const CrateReaderConfig &config = CrateReaderConfig()); + ~CrateReader(); + + bool ReadBootStrap(); + bool ReadTOC(); + + /// + /// Read TOC section + /// + bool ReadSection(crate::Section *s); + + // Read known sections + bool ReadPaths(); + bool ReadTokens(); + bool ReadStrings(); + bool ReadFields(); + bool ReadFieldSets(); + bool ReadSpecs(); + + bool BuildLiveFieldSets(); + + std::string GetError(); + std::string GetWarning(); + + // Approximated memory usage in [mb] + size_t GetMemoryUsageInMB() const { + return size_t(_memoryUsage / 1024 / 1024); + } + + /// ------------------------------------- + /// Following Methods are valid after successfull parsing of Crate data. + /// + size_t NumNodes() const { return _nodes.size(); } + + const std::vector GetNodes() const { return _nodes; } + + const std::vector GetTokens() const { return _tokens; } + + const std::vector GetStringIndices() const { + return _string_indices; + } + + const std::vector &GetFields() const { return _fields; } + + const std::vector &GetFieldsetIndices() const { + return _fieldset_indices; + } + + const std::vector &GetPaths() const { return _paths; } + + const std::vector &GetElemPaths() const { return _elemPaths; } + + const std::vector &GetSpecs() const { return _specs; } + + const std::map &GetLiveFieldSets() const { + return _live_fieldsets; + } + +#if 0 + // FIXME: May not need this + const std::vector &GetPaths() const { + return _paths; + } +#endif + + const nonstd::optional GetToken(crate::Index token_index) const; + const nonstd::optional GetStringToken( + crate::Index string_index) const; + + bool HasField(const std::string &key) const; + nonstd::optional GetField(crate::Index index) const; + nonstd::optional GetFieldString(crate::Index index) const; + nonstd::optional GetSpecString(crate::Index index) const; + + size_t NumPaths() const { return _paths.size(); } + + nonstd::optional GetPath(crate::Index index) const; + nonstd::optional GetElementPath(crate::Index index) const; + nonstd::optional GetPathString(crate::Index index) const; + + /// + /// Find if a field with (`name`, `tyname`) exists in FieldValuePairVector. + /// + bool HasFieldValuePair(const FieldValuePairVector &fvs, + const std::string &name, const std::string &tyname); + + /// + /// Find if a field with `name`(type can be arbitrary) exists in + /// FieldValuePairVector. + /// + bool HasFieldValuePair(const FieldValuePairVector &fvs, + const std::string &name); + + nonstd::expected GetFieldValuePair( + const FieldValuePairVector &fvs, const std::string &name, + const std::string &tyname); + + nonstd::expected GetFieldValuePair( + const FieldValuePairVector &fvs, const std::string &name); + + // bool ParseAttribute(const FieldValuePairVector &fvs, + // PrimAttrib *attr, + // const std::string &prop_name); + + bool VersionGreaterThanOrEqualTo_0_8_0() const { + if (_version[0] > 0) { + return true; + } + + if (_version[1] >= 8) { + return true; + } + + return false; + } + + private: + +#if defined(TINYUSDZ_CRATE_USE_FOR_BASED_PATH_INDEX_DECODER) + // To save stack usage + struct BuildDecompressedPathsArg { + std::vector *pathIndexes{}; + std::vector *elementTokenIndexes{}; + std::vector *jumps{}; + std::vector *visit_table{}; + size_t startIndex{}; // usually 0 + size_t endIndex{}; // inclusive. usually pathIndexes.size() - 1 + Path parentPath; + }; + + bool BuildDecompressedPathsImpl( + BuildDecompressedPathsArg *arg); + +#else + bool BuildDecompressedPathsImpl( + std::vector const &pathIndexes, + std::vector const &elementTokenIndexes, + std::vector const &jumps, + std::vector &visit_table, // track visited pathIndex to prevent + // circular referencing + size_t curIndex, const Path &parentPath); +#endif + + bool UnpackValueRep(const crate::ValueRep &rep, crate::CrateValue *value); + bool UnpackInlinedValueRep(const crate::ValueRep &rep, + crate::CrateValue *value); + + // + // Construct node hierarchy. + // + bool BuildNodeHierarchy( + std::vector const &pathIndexes, + std::vector const &elementTokenIndexes, + std::vector const &jumps, + std::vector &visit_table, // track visited pathIndex to prevent + // circular referencing + size_t curIndex, int64_t parentNodeIndex); + + bool ReadCompressedPaths(const uint64_t ref_num_paths); + + template + bool ReadCompressedInts(Int *out, size_t num_elements); + + bool ReadIndices(std::vector *is); + bool ReadIndex(crate::Index *i); + bool ReadString(std::string *s); + bool ReadValueRep(crate::ValueRep *rep); + + bool ReadPathArray(std::vector *d); + bool ReadStringArray(std::vector *d); + bool ReadLayerOffsetArray(std::vector *d); + + bool ReadReference(Reference *d); + bool ReadPayload(Payload *d); + bool ReadLayerOffset(LayerOffset *d); + + // customData(Dictionary) + bool ReadCustomData(CustomDataType *d); + + bool ReadTimeSamples(value::TimeSamples *d); + + // integral array + template + bool ReadIntArray(bool is_compressed, std::vector *d); + + bool ReadHalfArray(bool is_compressed, std::vector *d); + bool ReadFloatArray(bool is_compressed, std::vector *d); + bool ReadDoubleArray(bool is_compressed, std::vector *d); + + // template + // struct IsIntType { + // static const bool value = + // std::is_same::value || + // std::is_same::value || + // std::is_same::value || + // std::is_same::value; + // }; + + template + bool ReadArray(std::vector *d); + + // template ::value, bool>::type> + // bool ReadArray(std::vector *d); + + template + bool ReadListOp(ListOp *d); + + // TODO: Templatize + bool ReadPathListOp(ListOp *d); + bool ReadTokenListOp(ListOp *d); + bool ReadStringListOp(ListOp *d); + // bool ReadIntListOp(ListOp *d); + // bool ReadUIntListOp(ListOp *d); + // bool ReadInt64ListOp(ListOp *d); + // bool ReadUInt64ListOp(ListOp *d); + // bool ReadReferenceListOp(ListOp *d); + // bool ReadPayloadListOp(ListOp *d); + + bool ReadVariantSelectionMap(VariantSelectionMap *d); + + // Read 64bit uint with range check + bool ReadNum(uint64_t &n, uint64_t maxnum); + + // Header(bootstrap) + uint8_t _version[3] = {0, 0, 0}; + + crate::TableOfContents _toc; + + int64_t _toc_offset{0}; + + // index to _toc.sections + int64_t _tokens_index{-1}; + int64_t _paths_index{-1}; + int64_t _strings_index{-1}; + int64_t _fields_index{-1}; + int64_t _fieldsets_index{-1}; + int64_t _specs_index{-1}; + + std::vector _tokens; + std::vector _string_indices; + std::vector _fields; + std::vector _fieldset_indices; + std::vector _specs; + std::vector _paths; + std::vector _elemPaths; + + std::vector _nodes; // [0] = root node + // + // `_live_fieldsets` contains unpacked value keyed by fieldset index. + // Used for reconstructing Scene object + // TODO(syoyo): Use unordered_map? + std::map + _live_fieldsets; //
+ + const StreamReader *_sr{}; + + void PushError(const std::string &s) const { _err += s; } + void PushWarn(const std::string &s) const { _warn += s; } + mutable std::string _err; + mutable std::string _warn; + + // To prevent recursive Value unpack(The Value encodes itself) + std::unordered_set unpackRecursionGuard; + + CrateReaderConfig _config; + + // Approximated uncompressed memory usage(vertices, `tokens`, ...) in bytes. + uint64_t _memoryUsage{0}; + + class Impl; + Impl *_impl; +}; + +} // namespace crate +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/define-type-trait.inc b/contrib/tinyusdz/tinyusdz_repo/src/define-type-trait.inc new file mode 100644 index 000000000..e9c700dba --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/define-type-trait.inc @@ -0,0 +1,40 @@ +// TODO: Use (inline) constexpr for C++17 +// TODO: Deprecate ndim() +#define DEFINE_TYPE_TRAIT(__dty, __name, __tyid, __nc) \ + template <> \ + struct TypeTraits<__dty> { \ + using value_type = __dty; \ + using value_underlying_type = __dty; \ + static constexpr uint32_t ndim() { return 0; } /* array dim */ \ + static constexpr size_t size() { return sizeof(__dty); } \ + static constexpr uint32_t ncomp() { \ + return __nc; \ + } /* the number of components(e.g. extent = 2) */ \ + static constexpr uint32_t type_id() { return __tyid; } \ + static constexpr uint32_t underlying_type_id() { return __tyid; } \ + static std::string type_name() { return __name; } \ + static std::string underlying_type_name() { return __name; } \ + static bool is_role_type() { return false; } \ + static bool is_array() { return type_id() & value::TYPE_ID_1D_ARRAY_BIT; } \ + } + +// `role` type. Requies underlying type. +#define DEFINE_ROLE_TYPE_TRAIT(__dty, __name, __tyid, __uty) \ + template <> \ + struct TypeTraits<__dty> { \ + using value_type = __dty; \ + using value_underlying_type = TypeTraits<__uty>::value_type; \ + static constexpr uint32_t ndim() { return 0; } /* array dim */ \ + static constexpr size_t size() { return TypeTraits<__uty>::size(); } \ + static constexpr uint32_t ncomp() { return TypeTraits<__uty>::ncomp(); } \ + static constexpr uint32_t type_id() { return __tyid; } \ + static constexpr uint32_t underlying_type_id() { \ + return TypeTraits<__uty>::type_id(); \ + } \ + static std::string type_name() { return __name; } \ + static std::string underlying_type_name() { \ + return TypeTraits<__uty>::type_name(); \ + } \ + static bool is_role_type() { return true; } \ + static bool is_array() { return type_id() & value::TYPE_ID_1D_ARRAY_BIT; } \ + } diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/dtoa_milo.LICENSE b/contrib/tinyusdz/tinyusdz_repo/src/external/dtoa_milo.LICENSE new file mode 100644 index 000000000..3e0c6f71f --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/dtoa_milo.LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2014 Milo Yip + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/dtoa_milo.h b/contrib/tinyusdz/tinyusdz_repo/src/external/dtoa_milo.h new file mode 100644 index 000000000..ac2309242 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/dtoa_milo.h @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: MIT +// Copyright (C) 2014 Milo Yip +// https://github.com/miloyip/dtoa-benchmark +// +#pragma once +#include +#include +#include + +//#if defined(_MSC_VER) +//#include "msinttypes/stdint.h" +//#include +//#else +// cstdint should be available for VS2019 or later +#include +//#endif + +// TINYUSDZ: TODO: Completely disable int128 feature for portablity? +#if defined(__GNUC__) +#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && \ + defined(__x86_64__) +namespace gcc_ints +{ + __extension__ typedef __int128 int128; + __extension__ typedef unsigned __int128 uint128; +} // namespace gcc_ints +#endif +#endif + +#define UINT64_C2(h, l) ((static_cast(h) << 32) | static_cast(l)) + +struct DiyFp { + DiyFp() {} + + DiyFp(uint64_t _f, int _e) : f(_f), e(_e) {} + + DiyFp(double d) { + union { + double d; + uint64_t u64; + } u = { d }; + + int biased_e = (u.u64 & kDpExponentMask) >> kDpSignificandSize; + uint64_t significand = (u.u64 & kDpSignificandMask); + if (biased_e != 0) { + f = significand + kDpHiddenBit; + e = biased_e - kDpExponentBias; + } + else { + f = significand; + e = kDpMinExponent + 1; + } + } + + DiyFp operator-(const DiyFp& rhs) const { + assert(e == rhs.e); + assert(f >= rhs.f); + return DiyFp(f - rhs.f, e); + } + + DiyFp operator*(const DiyFp& rhs) const { +#if defined(_MSC_VER) && defined(_M_AMD64) + uint64_t h; + uint64_t l = _umul128(f, rhs.f, &h); + if (l & (uint64_t(1) << 63)) // rounding + h++; + return DiyFp(h, e + rhs.e + 64); +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) + gcc_ints::uint128 p = static_cast(f) * static_cast(rhs.f); + uint64_t h = p >> 64; + uint64_t l = static_cast(p); + if (l & (uint64_t(1) << 63)) // rounding + h++; + return DiyFp(h, e + rhs.e + 64); +#else + const uint64_t M32 = 0xFFFFFFFF; + const uint64_t a = f >> 32; + const uint64_t b = f & M32; + const uint64_t c = rhs.f >> 32; + const uint64_t d = rhs.f & M32; + const uint64_t ac = a * c; + const uint64_t bc = b * c; + const uint64_t ad = a * d; + const uint64_t bd = b * d; + uint64_t tmp = (bd >> 32) + (ad & M32) + (bc & M32); + tmp += 1U << 31; /// mult_round + return DiyFp(ac + (ad >> 32) + (bc >> 32) + (tmp >> 32), e + rhs.e + 64); +#endif + } + + DiyFp Normalize() const { +#if defined(_MSC_VER) && defined(_M_AMD64) + unsigned long index; + _BitScanReverse64(&index, f); + return DiyFp(f << (63 - int(index)), e - (63 - int(index))); +#elif defined(__GNUC__) + int s = __builtin_clzll(f); + return DiyFp(f << s, e - s); +#else + DiyFp res = *this; + while (!(res.f & kDpHiddenBit)) { + res.f <<= 1; + res.e--; + } + res.f <<= (kDiySignificandSize - kDpSignificandSize - 1); + res.e = res.e - (kDiySignificandSize - kDpSignificandSize - 1); + return res; +#endif + } + + DiyFp NormalizeBoundary() const { +#if defined(_MSC_VER) && defined(_M_AMD64) + unsigned long index; + _BitScanReverse64(&index, f); + return DiyFp (f << (63 - int(index)), e - (63 - int(index))); +#else + DiyFp res = *this; + while (!(res.f & (kDpHiddenBit << 1))) { + res.f <<= 1; + res.e--; + } + res.f <<= (kDiySignificandSize - kDpSignificandSize - 2); + res.e = res.e - (kDiySignificandSize - kDpSignificandSize - 2); + return res; +#endif + } + + void NormalizedBoundaries(DiyFp* minus, DiyFp* plus) const { + DiyFp pl = DiyFp((f << 1) + 1, e - 1).NormalizeBoundary(); + DiyFp mi = (f == kDpHiddenBit) ? DiyFp((f << 2) - 1, e - 2) : DiyFp((f << 1) - 1, e - 1); + mi.f <<= mi.e - pl.e; + mi.e = pl.e; + *plus = pl; + *minus = mi; + } + + static const int kDiySignificandSize = 64; + static const int kDpSignificandSize = 52; + static const int kDpExponentBias = 0x3FF + kDpSignificandSize; + static const int kDpMinExponent = -kDpExponentBias; + static const uint64_t kDpExponentMask = UINT64_C2(0x7FF00000, 0x00000000); + static const uint64_t kDpSignificandMask = UINT64_C2(0x000FFFFF, 0xFFFFFFFF); + static const uint64_t kDpHiddenBit = UINT64_C2(0x00100000, 0x00000000); + + uint64_t f; + int e; +}; + +inline DiyFp GetCachedPower(int e, int* K) { + // 10^-348, 10^-340, ..., 10^340 + static const uint64_t kCachedPowers_F[] = { + UINT64_C2(0xfa8fd5a0, 0x081c0288), UINT64_C2(0xbaaee17f, 0xa23ebf76), + UINT64_C2(0x8b16fb20, 0x3055ac76), UINT64_C2(0xcf42894a, 0x5dce35ea), + UINT64_C2(0x9a6bb0aa, 0x55653b2d), UINT64_C2(0xe61acf03, 0x3d1a45df), + UINT64_C2(0xab70fe17, 0xc79ac6ca), UINT64_C2(0xff77b1fc, 0xbebcdc4f), + UINT64_C2(0xbe5691ef, 0x416bd60c), UINT64_C2(0x8dd01fad, 0x907ffc3c), + UINT64_C2(0xd3515c28, 0x31559a83), UINT64_C2(0x9d71ac8f, 0xada6c9b5), + UINT64_C2(0xea9c2277, 0x23ee8bcb), UINT64_C2(0xaecc4991, 0x4078536d), + UINT64_C2(0x823c1279, 0x5db6ce57), UINT64_C2(0xc2109436, 0x4dfb5637), + UINT64_C2(0x9096ea6f, 0x3848984f), UINT64_C2(0xd77485cb, 0x25823ac7), + UINT64_C2(0xa086cfcd, 0x97bf97f4), UINT64_C2(0xef340a98, 0x172aace5), + UINT64_C2(0xb23867fb, 0x2a35b28e), UINT64_C2(0x84c8d4df, 0xd2c63f3b), + UINT64_C2(0xc5dd4427, 0x1ad3cdba), UINT64_C2(0x936b9fce, 0xbb25c996), + UINT64_C2(0xdbac6c24, 0x7d62a584), UINT64_C2(0xa3ab6658, 0x0d5fdaf6), + UINT64_C2(0xf3e2f893, 0xdec3f126), UINT64_C2(0xb5b5ada8, 0xaaff80b8), + UINT64_C2(0x87625f05, 0x6c7c4a8b), UINT64_C2(0xc9bcff60, 0x34c13053), + UINT64_C2(0x964e858c, 0x91ba2655), UINT64_C2(0xdff97724, 0x70297ebd), + UINT64_C2(0xa6dfbd9f, 0xb8e5b88f), UINT64_C2(0xf8a95fcf, 0x88747d94), + UINT64_C2(0xb9447093, 0x8fa89bcf), UINT64_C2(0x8a08f0f8, 0xbf0f156b), + UINT64_C2(0xcdb02555, 0x653131b6), UINT64_C2(0x993fe2c6, 0xd07b7fac), + UINT64_C2(0xe45c10c4, 0x2a2b3b06), UINT64_C2(0xaa242499, 0x697392d3), + UINT64_C2(0xfd87b5f2, 0x8300ca0e), UINT64_C2(0xbce50864, 0x92111aeb), + UINT64_C2(0x8cbccc09, 0x6f5088cc), UINT64_C2(0xd1b71758, 0xe219652c), + UINT64_C2(0x9c400000, 0x00000000), UINT64_C2(0xe8d4a510, 0x00000000), + UINT64_C2(0xad78ebc5, 0xac620000), UINT64_C2(0x813f3978, 0xf8940984), + UINT64_C2(0xc097ce7b, 0xc90715b3), UINT64_C2(0x8f7e32ce, 0x7bea5c70), + UINT64_C2(0xd5d238a4, 0xabe98068), UINT64_C2(0x9f4f2726, 0x179a2245), + UINT64_C2(0xed63a231, 0xd4c4fb27), UINT64_C2(0xb0de6538, 0x8cc8ada8), + UINT64_C2(0x83c7088e, 0x1aab65db), UINT64_C2(0xc45d1df9, 0x42711d9a), + UINT64_C2(0x924d692c, 0xa61be758), UINT64_C2(0xda01ee64, 0x1a708dea), + UINT64_C2(0xa26da399, 0x9aef774a), UINT64_C2(0xf209787b, 0xb47d6b85), + UINT64_C2(0xb454e4a1, 0x79dd1877), UINT64_C2(0x865b8692, 0x5b9bc5c2), + UINT64_C2(0xc83553c5, 0xc8965d3d), UINT64_C2(0x952ab45c, 0xfa97a0b3), + UINT64_C2(0xde469fbd, 0x99a05fe3), UINT64_C2(0xa59bc234, 0xdb398c25), + UINT64_C2(0xf6c69a72, 0xa3989f5c), UINT64_C2(0xb7dcbf53, 0x54e9bece), + UINT64_C2(0x88fcf317, 0xf22241e2), UINT64_C2(0xcc20ce9b, 0xd35c78a5), + UINT64_C2(0x98165af3, 0x7b2153df), UINT64_C2(0xe2a0b5dc, 0x971f303a), + UINT64_C2(0xa8d9d153, 0x5ce3b396), UINT64_C2(0xfb9b7cd9, 0xa4a7443c), + UINT64_C2(0xbb764c4c, 0xa7a44410), UINT64_C2(0x8bab8eef, 0xb6409c1a), + UINT64_C2(0xd01fef10, 0xa657842c), UINT64_C2(0x9b10a4e5, 0xe9913129), + UINT64_C2(0xe7109bfb, 0xa19c0c9d), UINT64_C2(0xac2820d9, 0x623bf429), + UINT64_C2(0x80444b5e, 0x7aa7cf85), UINT64_C2(0xbf21e440, 0x03acdd2d), + UINT64_C2(0x8e679c2f, 0x5e44ff8f), UINT64_C2(0xd433179d, 0x9c8cb841), + UINT64_C2(0x9e19db92, 0xb4e31ba9), UINT64_C2(0xeb96bf6e, 0xbadf77d9), + UINT64_C2(0xaf87023b, 0x9bf0ee6b) + }; + static const int16_t kCachedPowers_E[] = { + -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, + -954, -927, -901, -874, -847, -821, -794, -768, -741, -715, + -688, -661, -635, -608, -582, -555, -529, -502, -475, -449, + -422, -396, -369, -343, -316, -289, -263, -236, -210, -183, + -157, -130, -103, -77, -50, -24, 3, 30, 56, 83, + 109, 136, 162, 189, 216, 242, 269, 295, 322, 348, + 375, 402, 428, 455, 481, 508, 534, 561, 588, 614, + 641, 667, 694, 720, 747, 774, 800, 827, 853, 880, + 907, 933, 960, 986, 1013, 1039, 1066 + }; + + //int k = static_cast(ceil((-61 - e) * 0.30102999566398114)) + 374; + double dk = (-61 - e) * 0.30102999566398114 + 347; // dk must be positive, so can do ceiling in positive + int k = static_cast(dk); + if (dk - k > 0.0) + k++; + + unsigned index = static_cast((k >> 3) + 1); + *K = -(-348 + static_cast(index << 3)); // decimal exponent no need lookup table + + assert(index < sizeof(kCachedPowers_F) / sizeof(kCachedPowers_F[0])); + return DiyFp(kCachedPowers_F[index], kCachedPowers_E[index]); +} + +inline void GrisuRound(char* buffer, int len, uint64_t delta, uint64_t rest, uint64_t ten_kappa, uint64_t wp_w) { + while (rest < wp_w && delta - rest >= ten_kappa && + (rest + ten_kappa < wp_w || /// closer + wp_w - rest > rest + ten_kappa - wp_w)) { + buffer[len - 1]--; + rest += ten_kappa; + } +} + +inline unsigned CountDecimalDigit32(uint32_t n) { + // Simple pure C++ implementation was faster than __builtin_clz version in this situation. + if (n < 10) return 1; + if (n < 100) return 2; + if (n < 1000) return 3; + if (n < 10000) return 4; + if (n < 100000) return 5; + if (n < 1000000) return 6; + if (n < 10000000) return 7; + if (n < 100000000) return 8; + if (n < 1000000000) return 9; + return 10; +} + +inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buffer, int* len, int* K) { + static const uint32_t kPow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; + const DiyFp one(uint64_t(1) << -Mp.e, Mp.e); + const DiyFp wp_w = Mp - W; + uint32_t p1 = static_cast(Mp.f >> -one.e); + uint64_t p2 = Mp.f & (one.f - 1); + int kappa = static_cast(CountDecimalDigit32(p1)); + *len = 0; + + while (kappa > 0) { + uint32_t d; + switch (kappa) { + case 10: d = p1 / 1000000000; p1 %= 1000000000; break; + case 9: d = p1 / 100000000; p1 %= 100000000; break; + case 8: d = p1 / 10000000; p1 %= 10000000; break; + case 7: d = p1 / 1000000; p1 %= 1000000; break; + case 6: d = p1 / 100000; p1 %= 100000; break; + case 5: d = p1 / 10000; p1 %= 10000; break; + case 4: d = p1 / 1000; p1 %= 1000; break; + case 3: d = p1 / 100; p1 %= 100; break; + case 2: d = p1 / 10; p1 %= 10; break; + case 1: d = p1; p1 = 0; break; + default: +#if defined(_MSC_VER) + __assume(0); +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) + __builtin_unreachable(); +#else + d = 0; +#endif + } + if (d || *len) + buffer[(*len)++] = '0' + static_cast(d); + kappa--; + uint64_t tmp = (static_cast(p1) << -one.e) + p2; + if (tmp <= delta) { + *K += kappa; + GrisuRound(buffer, *len, delta, tmp, static_cast(kPow10[kappa]) << -one.e, wp_w.f); + return; + } + } + + // kappa = 0 + for (;;) { + p2 *= 10; + delta *= 10; + char d = static_cast(p2 >> -one.e); + if (d || *len) + buffer[(*len)++] = '0' + d; + p2 &= one.f - 1; + kappa--; + if (p2 < delta) { + *K += kappa; + GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * kPow10[-kappa]); + return; + } + } +} + +inline void Grisu2(double value, char* buffer, int* length, int* K) { + const DiyFp v(value); + DiyFp w_m, w_p; + v.NormalizedBoundaries(&w_m, &w_p); + + const DiyFp c_mk = GetCachedPower(w_p.e, K); + const DiyFp W = v.Normalize() * c_mk; + DiyFp Wp = w_p * c_mk; + DiyFp Wm = w_m * c_mk; + Wm.f++; + Wp.f--; + DigitGen(W, Wp, Wp.f - Wm.f, buffer, length, K); +} + +inline const char* GetDigitsLut() { + static const char cDigitsLut[200] = { + '0', '0', '0', '1', '0', '2', '0', '3', '0', '4', '0', '5', '0', '6', '0', '7', '0', '8', '0', '9', + '1', '0', '1', '1', '1', '2', '1', '3', '1', '4', '1', '5', '1', '6', '1', '7', '1', '8', '1', '9', + '2', '0', '2', '1', '2', '2', '2', '3', '2', '4', '2', '5', '2', '6', '2', '7', '2', '8', '2', '9', + '3', '0', '3', '1', '3', '2', '3', '3', '3', '4', '3', '5', '3', '6', '3', '7', '3', '8', '3', '9', + '4', '0', '4', '1', '4', '2', '4', '3', '4', '4', '4', '5', '4', '6', '4', '7', '4', '8', '4', '9', + '5', '0', '5', '1', '5', '2', '5', '3', '5', '4', '5', '5', '5', '6', '5', '7', '5', '8', '5', '9', + '6', '0', '6', '1', '6', '2', '6', '3', '6', '4', '6', '5', '6', '6', '6', '7', '6', '8', '6', '9', + '7', '0', '7', '1', '7', '2', '7', '3', '7', '4', '7', '5', '7', '6', '7', '7', '7', '8', '7', '9', + '8', '0', '8', '1', '8', '2', '8', '3', '8', '4', '8', '5', '8', '6', '8', '7', '8', '8', '8', '9', + '9', '0', '9', '1', '9', '2', '9', '3', '9', '4', '9', '5', '9', '6', '9', '7', '9', '8', '9', '9' + }; + return cDigitsLut; +} + +inline void WriteExponent(int K, char* buffer) { + if (K < 0) { + *buffer++ = '-'; + K = -K; + } + + if (K >= 100) { + *buffer++ = '0' + static_cast(K / 100); + K %= 100; + const char* d = GetDigitsLut() + K * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } + else if (K >= 10) { + const char* d = GetDigitsLut() + K * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } + else + *buffer++ = '0' + static_cast(K); + + *buffer = '\0'; +} + +inline void Prettify(char* buffer, int length, int k) { + const int kk = length + k; // 10^(kk-1) <= v < 10^kk + + if (length <= kk && kk <= 21) { + // 1234e7 -> 12340000000 + for (int i = length; i < kk; i++) + buffer[i] = '0'; + buffer[kk] = '.'; + buffer[kk + 1] = '0'; + buffer[kk + 2] = '\0'; + } + else if (0 < kk && kk <= 21) { + // 1234e-2 -> 12.34 + memmove(&buffer[kk + 1], &buffer[kk], size_t(length - kk)); + buffer[kk] = '.'; + buffer[length + 1] = '\0'; + } + else if (-6 < kk && kk <= 0) { + // 1234e-6 -> 0.001234 + const int offset = 2 - kk; + memmove(&buffer[offset], &buffer[0], size_t(length)); + buffer[0] = '0'; + buffer[1] = '.'; + for (int i = 2; i < offset; i++) + buffer[i] = '0'; + buffer[length + offset] = '\0'; + } + else if (length == 1) { + // 1e30 + buffer[1] = 'e'; + WriteExponent(kk - 1, &buffer[2]); + } + else { + // 1234e30 -> 1.234e33 + memmove(&buffer[2], &buffer[1], size_t(length - 1)); + buffer[1] = '.'; + buffer[length + 1] = 'e'; + WriteExponent(kk - 1, &buffer[0 + length + 2]); + } +} + +inline void dtoa_milo(double value, char* buffer) { + // Not handling NaN and inf + assert(!std::isnan(value)); + assert(!std::isinf(value)); + + if (std::fabs(value) < std::numeric_limits::epsilon()) { + buffer[0] = '0'; + buffer[1] = '.'; + buffer[2] = '0'; + buffer[3] = '\0'; + } + else { + if (value < 0) { + *buffer++ = '-'; + value = -value; + } + int length, K; + Grisu2(value, buffer, &length, &K); + Prettify(buffer, length, K); + } +} diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/LICENSE-APACHE b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/LICENSE-APACHE new file mode 100644 index 000000000..26f4398f2 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/LICENSE-APACHE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2021 The fast_float authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/LICENSE-BOOST b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/LICENSE-BOOST new file mode 100644 index 000000000..127a5bc39 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/LICENSE-BOOST @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/LICENSE-MIT b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/LICENSE-MIT new file mode 100644 index 000000000..2fb2a37ad --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/LICENSE-MIT @@ -0,0 +1,27 @@ +MIT License + +Copyright (c) 2021 The fast_float authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/README.md b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/README.md new file mode 100644 index 000000000..f12dea551 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/README.md @@ -0,0 +1,291 @@ + +## fast_float number parsing library: 4x faster than strtod +[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/fast_float.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:fast_float) +[![VS17-CI](https://github.com/fastfloat/fast_float/actions/workflows/vs17-ci.yml/badge.svg)](https://github.com/fastfloat/fast_float/actions/workflows/vs17-ci.yml) +[![Ubuntu 22.04 CI (GCC 11)](https://github.com/fastfloat/fast_float/actions/workflows/ubuntu22.yml/badge.svg)](https://github.com/fastfloat/fast_float/actions/workflows/ubuntu22.yml) + +The fast_float library provides fast header-only implementations for the C++ from_chars +functions for `float` and `double` types. These functions convert ASCII strings representing +decimal values (e.g., `1.3e10`) into binary types. We provide exact rounding (including +round to even). In our experience, these `fast_float` functions many times faster than comparable number-parsing functions from existing C++ standard libraries. + +Specifically, `fast_float` provides the following two functions with a C++17-like syntax (the library itself only requires C++11): + +```C++ +from_chars_result from_chars(const char* first, const char* last, float& value, ...); +from_chars_result from_chars(const char* first, const char* last, double& value, ...); +``` + +The return type (`from_chars_result`) is defined as the struct: +```C++ +struct from_chars_result { + const char* ptr; + std::errc ec; +}; +``` + +It parses the character sequence [first,last) for a number. It parses floating-point numbers expecting +a locale-independent format equivalent to the C++17 from_chars function. +The resulting floating-point value is the closest floating-point values (using either float or double), +using the "round to even" convention for values that would otherwise fall right in-between two values. +That is, we provide exact parsing according to the IEEE standard. + + +Given a successful parse, the pointer (`ptr`) in the returned value is set to point right after the +parsed number, and the `value` referenced is set to the parsed value. In case of error, the returned +`ec` contains a representative error, otherwise the default (`std::errc()`) value is stored. + +The implementation does not throw and does not allocate memory (e.g., with `new` or `malloc`). + +It will parse infinity and nan values. + +Example: + +``` C++ +#include "fast_float/fast_float.h" +#include + +int main() { + const std::string input = "3.1416 xyz "; + double result; + auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result); + if(answer.ec != std::errc()) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; } + std::cout << "parsed the number " << result << std::endl; + return EXIT_SUCCESS; +} +``` + + +Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of +the type `fast_float::chars_format`. It is a bitset value: we check whether +`fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set +to determine whether we allow the fixed point and scientific notation respectively. +The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`. + +The library seeks to follow the C++17 (see [20.19.3](http://eel.is/c++draft/charconv.from.chars).(7.1)) specification. +* The `from_chars` function does not skip leading white-space characters. +* [A leading `+` sign](https://en.cppreference.com/w/cpp/utility/from_chars) is forbidden. +* It is generally impossible to represent a decimal value exactly as binary floating-point number (`float` and `double` types). We seek the nearest value. We round to an even mantissa when we are in-between two binary floating-point numbers. + +Furthermore, we have the following restrictions: +* We only support `float` and `double` types at this time. +* We only support the decimal format: we do not support hexadecimal strings. +* For values that are either very large or very small (e.g., `1e9999`), we represent it using the infinity or negative infinity value and the returned `ec` is set to `std::errc::result_out_of_range`. + +We support Visual Studio, macOS, Linux, freeBSD. We support big and little endian. We support 32-bit and 64-bit systems. + +We assume that the rounding mode is set to nearest (`std::fegetround() == FE_TONEAREST`). + +## C++20: compile-time evaluation (constexpr) + +In C++20, you may use `fast_float::from_chars` to parse strings +at compile-time, as in the following example: + +```C++ +// consteval forces compile-time evaluation of the function in C++20. +consteval double parse(std::string_view input) { + double result; + auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result); + if(answer.ec != std::errc()) { return -1.0; } + return result; +} + +// This function should compile to a function which +// merely returns 3.1415. +constexpr double constexptest() { + return parse("3.1415 input"); +} +``` + +## Non-ASCII Inputs + +We also support UTF-16 and UTF-32 inputs, as well as ASCII/UTF-8, as in the following example: + +``` C++ +#include "fast_float/fast_float.h" +#include + +int main() { + const std::u16string input = u"3.1416 xyz "; + double result; + auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result); + if(answer.ec != std::errc()) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; } + std::cout << "parsed the number " << result << std::endl; + return EXIT_SUCCESS; +} +``` + +## Using commas as decimal separator + + +The C++ standard stipulate that `from_chars` has to be locale-independent. In +particular, the decimal separator has to be the period (`.`). However, +some users still want to use the `fast_float` library with in a locale-dependent +manner. Using a separate function called `from_chars_advanced`, we allow the users +to pass a `parse_options` instance which contains a custom decimal separator (e.g., +the comma). You may use it as follows. + +```C++ +#include "fast_float/fast_float.h" +#include + +int main() { + const std::string input = "3,1416 xyz "; + double result; + fast_float::parse_options options{fast_float::chars_format::general, ','}; + auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options); + if((answer.ec != std::errc()) || ((result != 3.1416))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; } + std::cout << "parsed the number " << result << std::endl; + return EXIT_SUCCESS; +} +``` + +You can parse delimited numbers: +```C++ + const std::string input = "234532.3426362,7869234.9823,324562.645"; + double result; + auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result); + if(answer.ec != std::errc()) { + // check error + } + // we have result == 234532.3426362. + if(answer.ptr[0] != ',') { + // unexpected delimiter + } + answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result); + if(answer.ec != std::errc()) { + // check error + } + // we have result == 7869234.9823. + if(answer.ptr[0] != ',') { + // unexpected delimiter + } + answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result); + if(answer.ec != std::errc()) { + // check error + } + // we have result == 324562.645. +``` + + +## Relation With Other Work + +The fast_float library is part of: + +- GCC (as of version 12): the `from_chars` function in GCC relies on fast_float. +- [WebKit](https://github.com/WebKit/WebKit), the engine behind Safari (Apple's web browser) + + +The fastfloat algorithm is part of the [LLVM standard libraries](https://github.com/llvm/llvm-project/commit/87c016078ad72c46505461e4ff8bfa04819fe7ba). + +There is a [derived implementation part of AdaCore](https://github.com/AdaCore/VSS). + + +The fast_float library provides a performance similar to that of the [fast_double_parser](https://github.com/lemire/fast_double_parser) library but using an updated algorithm reworked from the ground up, and while offering an API more in line with the expectations of C++ programmers. The fast_double_parser library is part of the [Microsoft LightGBM machine-learning framework](https://github.com/microsoft/LightGBM). + +## References + +- Daniel Lemire, [Number Parsing at a Gigabyte per Second](https://arxiv.org/abs/2101.11408), Software: Practice and Experience 51 (8), 2021. +- Noble Mushtak, Daniel Lemire, [Fast Number Parsing Without Fallback](https://arxiv.org/abs/2212.06644), Software: Practice and Experience 53 (7), 2023. + +## Other programming languages + +- [There is an R binding](https://github.com/eddelbuettel/rcppfastfloat) called `rcppfastfloat`. +- [There is a Rust port of the fast_float library](https://github.com/aldanor/fast-float-rust/) called `fast-float-rust`. +- [There is a Java port of the fast_float library](https://github.com/wrandelshofer/FastDoubleParser) called `FastDoubleParser`. It used for important systems such as [Jackson](https://github.com/FasterXML/jackson-core). +- [There is a C# port of the fast_float library](https://github.com/CarlVerret/csFastFloat) called `csFastFloat`. + + +## Users + +The fast_float library is used by [Apache Arrow](https://github.com/apache/arrow/pull/8494) where it multiplied the number parsing speed by two or three times. It is also used by [Yandex ClickHouse](https://github.com/ClickHouse/ClickHouse) and by [Google Jsonnet](https://github.com/google/jsonnet). + + +## How fast is it? + +It can parse random floating-point numbers at a speed of 1 GB/s on some systems. We find that it is often twice as fast as the best available competitor, and many times faster than many standard-library implementations. + + + +``` +$ ./build/benchmarks/benchmark +# parsing random integers in the range [0,1) +volume = 2.09808 MB +netlib : 271.18 MB/s (+/- 1.2 %) 12.93 Mfloat/s +doubleconversion : 225.35 MB/s (+/- 1.2 %) 10.74 Mfloat/s +strtod : 190.94 MB/s (+/- 1.6 %) 9.10 Mfloat/s +abseil : 430.45 MB/s (+/- 2.2 %) 20.52 Mfloat/s +fastfloat : 1042.38 MB/s (+/- 9.9 %) 49.68 Mfloat/s +``` + +See https://github.com/lemire/simple_fastfloat_benchmark for our benchmarking code. + + +## Video + +[![Go Systems 2020](http://img.youtube.com/vi/AVXgvlMeIm4/0.jpg)](http://www.youtube.com/watch?v=AVXgvlMeIm4)
+ +## Using as a CMake dependency + +This library is header-only by design. The CMake file provides the `fast_float` target +which is merely a pointer to the `include` directory. + +If you drop the `fast_float` repository in your CMake project, you should be able to use +it in this manner: + +```cmake +add_subdirectory(fast_float) +target_link_libraries(myprogram PUBLIC fast_float) +``` + +Or you may want to retrieve the dependency automatically if you have a sufficiently recent version of CMake (3.11 or better at least): + +```cmake +FetchContent_Declare( + fast_float + GIT_REPOSITORY https://github.com/lemire/fast_float.git + GIT_TAG tags/v1.1.2 + GIT_SHALLOW TRUE) + +FetchContent_MakeAvailable(fast_float) +target_link_libraries(myprogram PUBLIC fast_float) + +``` + +You should change the `GIT_TAG` line so that you recover the version you wish to use. + +## Using as single header + +The script `script/amalgamate.py` may be used to generate a single header +version of the library if so desired. +Just run the script from the root directory of this repository. +You can customize the license type and output file if desired as described in +the command line help. + +You may directly download automatically generated single-header files: + +https://github.com/fastfloat/fast_float/releases/download/v5.2.0/fast_float.h + +## Credit + +Though this work is inspired by many different people, this work benefited especially from exchanges with +Michael Eisel, who motivated the original research with his key insights, and with Nigel Tao who provided +invaluable feedback. Rémy Oudompheng first implemented a fast path we use in the case of long digits. + +The library includes code adapted from Google Wuffs (written by Nigel Tao) which was originally published +under the Apache 2.0 license. + +## License + + +Licensed under either of Apache License, Version +2.0 or MIT license or BOOST license . + + +
+ + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this repository by you, as defined in the Apache-2.0 license, +shall be triple licensed as above, without any additional terms or conditions. + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/ascii_number.h b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/ascii_number.h new file mode 100644 index 000000000..9afcdc4ec --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/ascii_number.h @@ -0,0 +1,417 @@ +#ifndef FASTFLOAT_ASCII_NUMBER_H +#define FASTFLOAT_ASCII_NUMBER_H + +#include +#include +#include +#include +#include + +#include "float_common.h" + +#ifdef FASTFLOAT_SSE2 +#include +#endif + +#ifdef FASTFLOAT_NEON +#include +#endif + +namespace fast_float { + +template +fastfloat_really_inline constexpr bool has_simd_opt() { +#ifdef FASTFLOAT_HAS_SIMD + return std::is_same::value; +#else + return false; +#endif +} + +// Next function can be micro-optimized, but compilers are entirely +// able to optimize it well. +template +fastfloat_really_inline constexpr bool is_integer(UC c) noexcept { + return !(c > UC('9') || c < UC('0')); +} + +fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) { + return (val & 0xFF00000000000000) >> 56 + | (val & 0x00FF000000000000) >> 40 + | (val & 0x0000FF0000000000) >> 24 + | (val & 0x000000FF00000000) >> 8 + | (val & 0x00000000FF000000) << 8 + | (val & 0x0000000000FF0000) << 24 + | (val & 0x000000000000FF00) << 40 + | (val & 0x00000000000000FF) << 56; +} + +// Read 8 UC into a u64. Truncates UC if not char. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +uint64_t read8_to_u64(const UC *chars) { + if (cpp20_and_in_constexpr() || !std::is_same::value) { + uint64_t val = 0; + for(int i = 0; i < 8; ++i) { + val |= uint64_t(uint8_t(*chars)) << (i*8); + ++chars; + } + return val; + } + uint64_t val; + ::memcpy(&val, chars, sizeof(uint64_t)); +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + return val; +} + +#ifdef FASTFLOAT_SSE2 + +fastfloat_really_inline +uint64_t simd_read8_to_u64(const __m128i data) { +FASTFLOAT_SIMD_DISABLE_WARNINGS + const __m128i packed = _mm_packus_epi16(data, data); +#ifdef FASTFLOAT_64BIT + return uint64_t(_mm_cvtsi128_si64(packed)); +#else + uint64_t value; + // Visual Studio + older versions of GCC don't support _mm_storeu_si64 + _mm_storel_epi64(reinterpret_cast<__m128i*>(&value), packed); + return value; +#endif +FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +fastfloat_really_inline +uint64_t simd_read8_to_u64(const char16_t* chars) { +FASTFLOAT_SIMD_DISABLE_WARNINGS + return simd_read8_to_u64(_mm_loadu_si128(reinterpret_cast(chars))); +FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +#elif defined(FASTFLOAT_NEON) + + +fastfloat_really_inline +uint64_t simd_read8_to_u64(const uint16x8_t data) { +FASTFLOAT_SIMD_DISABLE_WARNINGS + uint8x8_t utf8_packed = vmovn_u16(data); + return vget_lane_u64(vreinterpret_u64_u8(utf8_packed), 0); +FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +fastfloat_really_inline +uint64_t simd_read8_to_u64(const char16_t* chars) { +FASTFLOAT_SIMD_DISABLE_WARNINGS + return simd_read8_to_u64(vld1q_u16(reinterpret_cast(chars))); +FASTFLOAT_SIMD_RESTORE_WARNINGS +} + +#endif // FASTFLOAT_SSE2 + +// MSVC SFINAE is broken pre-VS2017 +#if defined(_MSC_VER) && _MSC_VER <= 1900 +template +#else +template ())> +#endif +// dummy for compile +uint64_t simd_read8_to_u64(UC const*) { + return 0; +} + + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +void write_u64(uint8_t *chars, uint64_t val) { + if (cpp20_and_in_constexpr()) { + for(int i = 0; i < 8; ++i) { + *chars = uint8_t(val); + val >>= 8; + ++chars; + } + return; + } +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + ::memcpy(chars, &val, sizeof(uint64_t)); +} + +// credit @aqrit +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 +uint32_t parse_eight_digits_unrolled(uint64_t val) { + const uint64_t mask = 0x000000FF000000FF; + const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return uint32_t(val); +} + + +// Call this if chars are definitely 8 digits. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +uint32_t parse_eight_digits_unrolled(UC const * chars) noexcept { + if (cpp20_and_in_constexpr() || !has_simd_opt()) { + return parse_eight_digits_unrolled(read8_to_u64(chars)); // truncation okay + } + return parse_eight_digits_unrolled(simd_read8_to_u64(chars)); +} + + +// credit @aqrit +fastfloat_really_inline constexpr bool is_made_of_eight_digits_fast(uint64_t val) noexcept { + return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & + 0x8080808080808080)); +} + + +#ifdef FASTFLOAT_HAS_SIMD + +// Call this if chars might not be 8 digits. +// Using this style (instead of is_made_of_eight_digits_fast() then parse_eight_digits_unrolled()) +// ensures we don't load SIMD registers twice. +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +bool simd_parse_if_eight_digits_unrolled(const char16_t* chars, uint64_t& i) noexcept { + if (cpp20_and_in_constexpr()) { + return false; + } +#ifdef FASTFLOAT_SSE2 +FASTFLOAT_SIMD_DISABLE_WARNINGS + const __m128i data = _mm_loadu_si128(reinterpret_cast(chars)); + + // (x - '0') <= 9 + // http://0x80.pl/articles/simd-parsing-int-sequences.html + const __m128i t0 = _mm_add_epi16(data, _mm_set1_epi16(32720)); + const __m128i t1 = _mm_cmpgt_epi16(t0, _mm_set1_epi16(-32759)); + + if (_mm_movemask_epi8(t1) == 0) { + i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); + return true; + } + else return false; +FASTFLOAT_SIMD_RESTORE_WARNINGS +#elif defined(FASTFLOAT_NEON) +FASTFLOAT_SIMD_DISABLE_WARNINGS + const uint16x8_t data = vld1q_u16(reinterpret_cast(chars)); + + // (x - '0') <= 9 + // http://0x80.pl/articles/simd-parsing-int-sequences.html + const uint16x8_t t0 = vsubq_u16(data, vmovq_n_u16('0')); + const uint16x8_t mask = vcltq_u16(t0, vmovq_n_u16('9' - '0' + 1)); + + if (vminvq_u16(mask) == 0xFFFF) { + i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); + return true; + } + else return false; +FASTFLOAT_SIMD_RESTORE_WARNINGS +#else + (void)chars; (void)i; + return false; +#endif // FASTFLOAT_SSE2 +} + +#endif // FASTFLOAT_HAS_SIMD + +// MSVC SFINAE is broken pre-VS2017 +#if defined(_MSC_VER) && _MSC_VER <= 1900 +template +#else +template ())> +#endif +// dummy for compile +bool simd_parse_if_eight_digits_unrolled(UC const*, uint64_t&) { + return 0; +} + + +template ::value)> +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +void loop_parse_if_eight_digits(const UC*& p, const UC* const pend, uint64_t& i) { + if (!has_simd_opt()) { + return; + } + while ((std::distance(p, pend) >= 8) && simd_parse_if_eight_digits_unrolled(p, i)) { // in rare cases, this will overflow, but that's ok + p += 8; + } +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +void loop_parse_if_eight_digits(const char*& p, const char* const pend, uint64_t& i) { + // optimizes better than parse_if_eight_digits_unrolled() for UC = char. + while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(read8_to_u64(p))) { + i = i * 100000000 + parse_eight_digits_unrolled(read8_to_u64(p)); // in rare cases, this will overflow, but that's ok + p += 8; + } +} + +template +struct parsed_number_string_t { + int64_t exponent{0}; + uint64_t mantissa{0}; + UC const * lastmatch{nullptr}; + bool negative{false}; + bool valid{false}; + bool too_many_digits{false}; + // contains the range of the significant digits + span integer{}; // non-nullable + span fraction{}; // nullable +}; + +using byte_span = span; +using parsed_number_string = parsed_number_string_t; + +// Assuming that you use no more than 19 digits, this will +// parse an ASCII string. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +parsed_number_string_t parse_number_string(UC const *p, UC const * pend, parse_options_t options) noexcept { + chars_format const fmt = options.format; + UC const decimal_point = options.decimal_point; + + parsed_number_string_t answer; + answer.valid = false; + answer.too_many_digits = false; + answer.negative = (*p == UC('-')); +#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default + if ((*p == UC('-')) || (*p == UC('+'))) { +#else + if (*p == UC('-')) { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here +#endif + ++p; + if (p == pend) { + return answer; + } + if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot + return answer; + } + } + UC const * const start_digits = p; + + uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) + + while ((p != pend) && is_integer(*p)) { + // a multiplication by 10 is cheaper than an arbitrary integer + // multiplication + i = 10 * i + + uint64_t(*p - UC('0')); // might overflow, we will handle the overflow later + ++p; + } + UC const * const end_of_integer_part = p; + int64_t digit_count = int64_t(end_of_integer_part - start_digits); + answer.integer = span(start_digits, size_t(digit_count)); + int64_t exponent = 0; + if ((p != pend) && (*p == decimal_point)) { + ++p; + UC const * before = p; + // can occur at most twice without overflowing, but let it occur more, since + // for integers with many digits, digit parsing is the primary bottleneck. + loop_parse_if_eight_digits(p, pend, i); + + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - UC('0')); + ++p; + i = i * 10 + digit; // in rare cases, this will overflow, but that's ok + } + exponent = before - p; + answer.fraction = span(before, size_t(p - before)); + digit_count -= exponent; + } + // we must have encountered at least one integer! + if (digit_count == 0) { + return answer; + } + int64_t exp_number = 0; // explicit exponential part + if ((fmt & chars_format::scientific) && (p != pend) && ((UC('e') == *p) || (UC('E') == *p))) { + UC const * location_of_e = p; + ++p; + bool neg_exp = false; + if ((p != pend) && (UC('-') == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && (UC('+') == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + if ((p == pend) || !is_integer(*p)) { + if(!(fmt & chars_format::fixed)) { + // We are in error. + return answer; + } + // Otherwise, we will be ignoring the 'e'. + p = location_of_e; + } else { + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - UC('0')); + if (exp_number < 0x10000000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + if(neg_exp) { exp_number = - exp_number; } + exponent += exp_number; + } + } else { + // If it scientific and not fixed, we have to bail out. + if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; } + } + answer.lastmatch = p; + answer.valid = true; + + // If we frequently had to deal with long strings of digits, + // we could extend our code by using a 128-bit integer instead + // of a 64-bit integer. However, this is uncommon. + // + // We can deal with up to 19 digits. + if (digit_count > 19) { // this is uncommon + // It is possible that the integer had an overflow. + // We have to handle the case where we have 0.0000somenumber. + // We need to be mindful of the case where we only have zeroes... + // E.g., 0.000000000...000. + UC const * start = start_digits; + while ((start != pend) && (*start == UC('0') || *start == decimal_point)) { + if(*start == UC('0')) { digit_count --; } + start++; + } + + if (digit_count > 19) { + answer.too_many_digits = true; + // Let us start again, this time, avoiding overflows. + // We don't need to check if is_integer, since we use the + // pre-tokenized spans from above. + i = 0; + p = answer.integer.ptr; + UC const* int_end = p + answer.integer.len(); + const uint64_t minimal_nineteen_digit_integer{ 1000000000000000000 }; + while ((i < minimal_nineteen_digit_integer) && (p != int_end)) { + i = i * 10 + uint64_t(*p - UC('0')); + ++p; + } + if (i >= minimal_nineteen_digit_integer) { // We have a big integers + exponent = end_of_integer_part - p + exp_number; + } + else { // We have a value with a fractional component. + p = answer.fraction.ptr; + UC const* frac_end = p + answer.fraction.len(); + while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) { + i = i * 10 + uint64_t(*p - UC('0')); + ++p; + } + exponent = answer.fraction.ptr - p + exp_number; + } + // We have now corrected both exponent and i, to a truncated value + } + } + answer.exponent = exponent; + answer.mantissa = i; + return answer; +} + +} // namespace fast_float + +#endif diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/bigint.h b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/bigint.h new file mode 100644 index 000000000..5076b47cc --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/bigint.h @@ -0,0 +1,617 @@ +#ifndef FASTFLOAT_BIGINT_H +#define FASTFLOAT_BIGINT_H + +#include +#include +#include +#include + +#include "float_common.h" + +namespace fast_float { + +// the limb width: we want efficient multiplication of double the bits in +// limb, or for 64-bit limbs, at least 64-bit multiplication where we can +// extract the high and low parts efficiently. this is every 64-bit +// architecture except for sparc, which emulates 128-bit multiplication. +// we might have platforms where `CHAR_BIT` is not 8, so let's avoid +// doing `8 * sizeof(limb)`. +#if defined(FASTFLOAT_64BIT) && !defined(__sparc) +#define FASTFLOAT_64BIT_LIMB 1 +typedef uint64_t limb; +constexpr size_t limb_bits = 64; +#else +#define FASTFLOAT_32BIT_LIMB +typedef uint32_t limb; +constexpr size_t limb_bits = 32; +#endif + +typedef span limb_span; + +// number of bits in a bigint. this needs to be at least the number +// of bits required to store the largest bigint, which is +// `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or +// ~3600 bits, so we round to 4000. +constexpr size_t bigint_bits = 4000; +constexpr size_t bigint_limbs = bigint_bits / limb_bits; + +// vector-like type that is allocated on the stack. the entire +// buffer is pre-allocated, and only the length changes. +template +struct stackvec { + limb data[size]; + // we never need more than 150 limbs + uint16_t length{0}; + + stackvec() = default; + stackvec(const stackvec &) = delete; + stackvec &operator=(const stackvec &) = delete; + stackvec(stackvec &&) = delete; + stackvec &operator=(stackvec &&other) = delete; + + // create stack vector from existing limb span. + FASTFLOAT_CONSTEXPR20 stackvec(limb_span s) { + FASTFLOAT_ASSERT(try_extend(s)); + } + + FASTFLOAT_CONSTEXPR14 limb& operator[](size_t index) noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return data[index]; + } + FASTFLOAT_CONSTEXPR14 const limb& operator[](size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return data[index]; + } + // index from the end of the container + FASTFLOAT_CONSTEXPR14 const limb& rindex(size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + size_t rindex = length - index - 1; + return data[rindex]; + } + + // set the length, without bounds checking. + FASTFLOAT_CONSTEXPR14 void set_len(size_t len) noexcept { + length = uint16_t(len); + } + constexpr size_t len() const noexcept { + return length; + } + constexpr bool is_empty() const noexcept { + return length == 0; + } + constexpr size_t capacity() const noexcept { + return size; + } + // append item to vector, without bounds checking + FASTFLOAT_CONSTEXPR14 void push_unchecked(limb value) noexcept { + data[length] = value; + length++; + } + // append item to vector, returning if item was added + FASTFLOAT_CONSTEXPR14 bool try_push(limb value) noexcept { + if (len() < capacity()) { + push_unchecked(value); + return true; + } else { + return false; + } + } + // add items to the vector, from a span, without bounds checking + FASTFLOAT_CONSTEXPR20 void extend_unchecked(limb_span s) noexcept { + limb* ptr = data + length; + std::copy_n(s.ptr, s.len(), ptr); + set_len(len() + s.len()); + } + // try to add items to the vector, returning if items were added + FASTFLOAT_CONSTEXPR20 bool try_extend(limb_span s) noexcept { + if (len() + s.len() <= capacity()) { + extend_unchecked(s); + return true; + } else { + return false; + } + } + // resize the vector, without bounds checking + // if the new size is longer than the vector, assign value to each + // appended item. + FASTFLOAT_CONSTEXPR20 + void resize_unchecked(size_t new_len, limb value) noexcept { + if (new_len > len()) { + size_t count = new_len - len(); + limb* first = data + len(); + limb* last = first + count; + ::std::fill(first, last, value); + set_len(new_len); + } else { + set_len(new_len); + } + } + // try to resize the vector, returning if the vector was resized. + FASTFLOAT_CONSTEXPR20 bool try_resize(size_t new_len, limb value) noexcept { + if (new_len > capacity()) { + return false; + } else { + resize_unchecked(new_len, value); + return true; + } + } + // check if any limbs are non-zero after the given index. + // this needs to be done in reverse order, since the index + // is relative to the most significant limbs. + FASTFLOAT_CONSTEXPR14 bool nonzero(size_t index) const noexcept { + while (index < len()) { + if (rindex(index) != 0) { + return true; + } + index++; + } + return false; + } + // normalize the big integer, so most-significant zero limbs are removed. + FASTFLOAT_CONSTEXPR14 void normalize() noexcept { + while (len() > 0 && rindex(0) == 0) { + length--; + } + } +}; + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 +uint64_t empty_hi64(bool& truncated) noexcept { + truncated = false; + return 0; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +uint64_t uint64_hi64(uint64_t r0, bool& truncated) noexcept { + truncated = false; + int shl = leading_zeroes(r0); + return r0 << shl; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +uint64_t uint64_hi64(uint64_t r0, uint64_t r1, bool& truncated) noexcept { + int shl = leading_zeroes(r0); + if (shl == 0) { + truncated = r1 != 0; + return r0; + } else { + int shr = 64 - shl; + truncated = (r1 << shl) != 0; + return (r0 << shl) | (r1 >> shr); + } +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +uint64_t uint32_hi64(uint32_t r0, bool& truncated) noexcept { + return uint64_hi64(r0, truncated); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +uint64_t uint32_hi64(uint32_t r0, uint32_t r1, bool& truncated) noexcept { + uint64_t x0 = r0; + uint64_t x1 = r1; + return uint64_hi64((x0 << 32) | x1, truncated); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +uint64_t uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool& truncated) noexcept { + uint64_t x0 = r0; + uint64_t x1 = r1; + uint64_t x2 = r2; + return uint64_hi64(x0, (x1 << 32) | x2, truncated); +} + +// add two small integers, checking for overflow. +// we want an efficient operation. for msvc, where +// we don't have built-in intrinsics, this is still +// pretty fast. +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +limb scalar_add(limb x, limb y, bool& overflow) noexcept { + limb z; +// gcc and clang +#if defined(__has_builtin) + #if __has_builtin(__builtin_add_overflow) + if (!cpp20_and_in_constexpr()) { + overflow = __builtin_add_overflow(x, y, &z); + return z; + } + #endif +#endif + + // generic, this still optimizes correctly on MSVC. + z = x + y; + overflow = z < x; + return z; +} + +// multiply two small integers, getting both the high and low bits. +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +limb scalar_mul(limb x, limb y, limb& carry) noexcept { +#ifdef FASTFLOAT_64BIT_LIMB + #if defined(__SIZEOF_INT128__) + // GCC and clang both define it as an extension. + __uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry); + carry = limb(z >> limb_bits); + return limb(z); + #else + // fallback, no native 128-bit integer multiplication with carry. + // on msvc, this optimizes identically, somehow. + value128 z = full_multiplication(x, y); + bool overflow; + z.low = scalar_add(z.low, carry, overflow); + z.high += uint64_t(overflow); // cannot overflow + carry = z.high; + return z.low; + #endif +#else + uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry); + carry = limb(z >> limb_bits); + return limb(z); +#endif +} + +// add scalar value to bigint starting from offset. +// used in grade school multiplication +template +inline FASTFLOAT_CONSTEXPR20 +bool small_add_from(stackvec& vec, limb y, size_t start) noexcept { + size_t index = start; + limb carry = y; + bool overflow; + while (carry != 0 && index < vec.len()) { + vec[index] = scalar_add(vec[index], carry, overflow); + carry = limb(overflow); + index += 1; + } + if (carry != 0) { + FASTFLOAT_TRY(vec.try_push(carry)); + } + return true; +} + +// add scalar value to bigint. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +bool small_add(stackvec& vec, limb y) noexcept { + return small_add_from(vec, y, 0); +} + +// multiply bigint by scalar value. +template +inline FASTFLOAT_CONSTEXPR20 +bool small_mul(stackvec& vec, limb y) noexcept { + limb carry = 0; + for (size_t index = 0; index < vec.len(); index++) { + vec[index] = scalar_mul(vec[index], y, carry); + } + if (carry != 0) { + FASTFLOAT_TRY(vec.try_push(carry)); + } + return true; +} + +// add bigint to bigint starting from index. +// used in grade school multiplication +template +FASTFLOAT_CONSTEXPR20 +bool large_add_from(stackvec& x, limb_span y, size_t start) noexcept { + // the effective x buffer is from `xstart..x.len()`, so exit early + // if we can't get that current range. + if (x.len() < start || y.len() > x.len() - start) { + FASTFLOAT_TRY(x.try_resize(y.len() + start, 0)); + } + + bool carry = false; + for (size_t index = 0; index < y.len(); index++) { + limb xi = x[index + start]; + limb yi = y[index]; + bool c1 = false; + bool c2 = false; + xi = scalar_add(xi, yi, c1); + if (carry) { + xi = scalar_add(xi, 1, c2); + } + x[index + start] = xi; + carry = c1 | c2; + } + + // handle overflow + if (carry) { + FASTFLOAT_TRY(small_add_from(x, 1, y.len() + start)); + } + return true; +} + +// add bigint to bigint. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +bool large_add_from(stackvec& x, limb_span y) noexcept { + return large_add_from(x, y, 0); +} + +// grade-school multiplication algorithm +template +FASTFLOAT_CONSTEXPR20 +bool long_mul(stackvec& x, limb_span y) noexcept { + limb_span xs = limb_span(x.data, x.len()); + stackvec z(xs); + limb_span zs = limb_span(z.data, z.len()); + + if (y.len() != 0) { + limb y0 = y[0]; + FASTFLOAT_TRY(small_mul(x, y0)); + for (size_t index = 1; index < y.len(); index++) { + limb yi = y[index]; + stackvec zi; + if (yi != 0) { + // re-use the same buffer throughout + zi.set_len(0); + FASTFLOAT_TRY(zi.try_extend(zs)); + FASTFLOAT_TRY(small_mul(zi, yi)); + limb_span zis = limb_span(zi.data, zi.len()); + FASTFLOAT_TRY(large_add_from(x, zis, index)); + } + } + } + + x.normalize(); + return true; +} + +// grade-school multiplication algorithm +template +FASTFLOAT_CONSTEXPR20 +bool large_mul(stackvec& x, limb_span y) noexcept { + if (y.len() == 1) { + FASTFLOAT_TRY(small_mul(x, y[0])); + } else { + FASTFLOAT_TRY(long_mul(x, y)); + } + return true; +} + +template +struct pow5_tables { + static constexpr uint32_t large_step = 135; + static constexpr uint64_t small_power_of_5[] = { + 1UL, 5UL, 25UL, 125UL, 625UL, 3125UL, 15625UL, 78125UL, 390625UL, + 1953125UL, 9765625UL, 48828125UL, 244140625UL, 1220703125UL, + 6103515625UL, 30517578125UL, 152587890625UL, 762939453125UL, + 3814697265625UL, 19073486328125UL, 95367431640625UL, 476837158203125UL, + 2384185791015625UL, 11920928955078125UL, 59604644775390625UL, + 298023223876953125UL, 1490116119384765625UL, 7450580596923828125UL, + }; +#ifdef FASTFLOAT_64BIT_LIMB + constexpr static limb large_power_of_5[] = { + 1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL, + 10482974169319127550UL, 198276706040285095UL}; +#else + constexpr static limb large_power_of_5[] = { + 4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U, + 1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U}; +#endif +}; + +template +constexpr uint32_t pow5_tables::large_step; + +template +constexpr uint64_t pow5_tables::small_power_of_5[]; + +template +constexpr limb pow5_tables::large_power_of_5[]; + +// big integer type. implements a small subset of big integer +// arithmetic, using simple algorithms since asymptotically +// faster algorithms are slower for a small number of limbs. +// all operations assume the big-integer is normalized. +struct bigint : pow5_tables<> { + // storage of the limbs, in little-endian order. + stackvec vec; + + FASTFLOAT_CONSTEXPR20 bigint(): vec() {} + bigint(const bigint &) = delete; + bigint &operator=(const bigint &) = delete; + bigint(bigint &&) = delete; + bigint &operator=(bigint &&other) = delete; + + FASTFLOAT_CONSTEXPR20 bigint(uint64_t value): vec() { +#ifdef FASTFLOAT_64BIT_LIMB + vec.push_unchecked(value); +#else + vec.push_unchecked(uint32_t(value)); + vec.push_unchecked(uint32_t(value >> 32)); +#endif + vec.normalize(); + } + + // get the high 64 bits from the vector, and if bits were truncated. + // this is to get the significant digits for the float. + FASTFLOAT_CONSTEXPR20 uint64_t hi64(bool& truncated) const noexcept { +#ifdef FASTFLOAT_64BIT_LIMB + if (vec.len() == 0) { + return empty_hi64(truncated); + } else if (vec.len() == 1) { + return uint64_hi64(vec.rindex(0), truncated); + } else { + uint64_t result = uint64_hi64(vec.rindex(0), vec.rindex(1), truncated); + truncated |= vec.nonzero(2); + return result; + } +#else + if (vec.len() == 0) { + return empty_hi64(truncated); + } else if (vec.len() == 1) { + return uint32_hi64(vec.rindex(0), truncated); + } else if (vec.len() == 2) { + return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated); + } else { + uint64_t result = uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated); + truncated |= vec.nonzero(3); + return result; + } +#endif + } + + // compare two big integers, returning the large value. + // assumes both are normalized. if the return value is + // negative, other is larger, if the return value is + // positive, this is larger, otherwise they are equal. + // the limbs are stored in little-endian order, so we + // must compare the limbs in ever order. + FASTFLOAT_CONSTEXPR20 int compare(const bigint& other) const noexcept { + if (vec.len() > other.vec.len()) { + return 1; + } else if (vec.len() < other.vec.len()) { + return -1; + } else { + for (size_t index = vec.len(); index > 0; index--) { + limb xi = vec[index - 1]; + limb yi = other.vec[index - 1]; + if (xi > yi) { + return 1; + } else if (xi < yi) { + return -1; + } + } + return 0; + } + } + + // shift left each limb n bits, carrying over to the new limb + // returns true if we were able to shift all the digits. + FASTFLOAT_CONSTEXPR20 bool shl_bits(size_t n) noexcept { + // Internally, for each item, we shift left by n, and add the previous + // right shifted limb-bits. + // For example, we transform (for u8) shifted left 2, to: + // b10100100 b01000010 + // b10 b10010001 b00001000 + FASTFLOAT_DEBUG_ASSERT(n != 0); + FASTFLOAT_DEBUG_ASSERT(n < sizeof(limb) * 8); + + size_t shl = n; + size_t shr = limb_bits - shl; + limb prev = 0; + for (size_t index = 0; index < vec.len(); index++) { + limb xi = vec[index]; + vec[index] = (xi << shl) | (prev >> shr); + prev = xi; + } + + limb carry = prev >> shr; + if (carry != 0) { + return vec.try_push(carry); + } + return true; + } + + // move the limbs left by `n` limbs. + FASTFLOAT_CONSTEXPR20 bool shl_limbs(size_t n) noexcept { + FASTFLOAT_DEBUG_ASSERT(n != 0); + if (n + vec.len() > vec.capacity()) { + return false; + } else if (!vec.is_empty()) { + // move limbs + limb* dst = vec.data + n; + const limb* src = vec.data; + std::copy_backward(src, src + vec.len(), dst + vec.len()); + // fill in empty limbs + limb* first = vec.data; + limb* last = first + n; + ::std::fill(first, last, 0); + vec.set_len(n + vec.len()); + return true; + } else { + return true; + } + } + + // move the limbs left by `n` bits. + FASTFLOAT_CONSTEXPR20 bool shl(size_t n) noexcept { + size_t rem = n % limb_bits; + size_t div = n / limb_bits; + if (rem != 0) { + FASTFLOAT_TRY(shl_bits(rem)); + } + if (div != 0) { + FASTFLOAT_TRY(shl_limbs(div)); + } + return true; + } + + // get the number of leading zeros in the bigint. + FASTFLOAT_CONSTEXPR20 int ctlz() const noexcept { + if (vec.is_empty()) { + return 0; + } else { +#ifdef FASTFLOAT_64BIT_LIMB + return leading_zeroes(vec.rindex(0)); +#else + // no use defining a specialized leading_zeroes for a 32-bit type. + uint64_t r0 = vec.rindex(0); + return leading_zeroes(r0 << 32); +#endif + } + } + + // get the number of bits in the bigint. + FASTFLOAT_CONSTEXPR20 int bit_length() const noexcept { + int lz = ctlz(); + return int(limb_bits * vec.len()) - lz; + } + + FASTFLOAT_CONSTEXPR20 bool mul(limb y) noexcept { + return small_mul(vec, y); + } + + FASTFLOAT_CONSTEXPR20 bool add(limb y) noexcept { + return small_add(vec, y); + } + + // multiply as if by 2 raised to a power. + FASTFLOAT_CONSTEXPR20 bool pow2(uint32_t exp) noexcept { + return shl(exp); + } + + // multiply as if by 5 raised to a power. + FASTFLOAT_CONSTEXPR20 bool pow5(uint32_t exp) noexcept { + // multiply by a power of 5 + size_t large_length = sizeof(large_power_of_5) / sizeof(limb); + limb_span large = limb_span(large_power_of_5, large_length); + while (exp >= large_step) { + FASTFLOAT_TRY(large_mul(vec, large)); + exp -= large_step; + } +#ifdef FASTFLOAT_64BIT_LIMB + uint32_t small_step = 27; + limb max_native = 7450580596923828125UL; +#else + uint32_t small_step = 13; + limb max_native = 1220703125U; +#endif + while (exp >= small_step) { + FASTFLOAT_TRY(small_mul(vec, max_native)); + exp -= small_step; + } + if (exp != 0) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + // This is similar to https://github.com/llvm/llvm-project/issues/47746, + // except the workaround described there don't work here + FASTFLOAT_TRY( + small_mul(vec, limb(((void)small_power_of_5[0], small_power_of_5[exp]))) + ); + } + + return true; + } + + // multiply as if by 10 raised to a power. + FASTFLOAT_CONSTEXPR20 bool pow10(uint32_t exp) noexcept { + FASTFLOAT_TRY(pow5(exp)); + return pow2(exp); + } +}; + +} // namespace fast_float + +#endif diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/constexpr_feature_detect.h b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/constexpr_feature_detect.h new file mode 100644 index 000000000..ba8b65c64 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/constexpr_feature_detect.h @@ -0,0 +1,40 @@ +#ifndef FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H +#define FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H + +#ifdef __has_include +#if __has_include() +#include +#endif +#endif + +// Testing for https://wg21.link/N3652, adopted in C++14 +#if __cpp_constexpr >= 201304 +#define FASTFLOAT_CONSTEXPR14 constexpr +#else +#define FASTFLOAT_CONSTEXPR14 +#endif + +#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L +#define FASTFLOAT_HAS_BIT_CAST 1 +#else +#define FASTFLOAT_HAS_BIT_CAST 0 +#endif + +#if defined(__cpp_lib_is_constant_evaluated) && __cpp_lib_is_constant_evaluated >= 201811L +#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1 +#else +#define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0 +#endif + +// Testing for relevant C++20 constexpr library features +#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED \ + && FASTFLOAT_HAS_BIT_CAST \ + && __cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/ +#define FASTFLOAT_CONSTEXPR20 constexpr +#define FASTFLOAT_IS_CONSTEXPR 1 +#else +#define FASTFLOAT_CONSTEXPR20 +#define FASTFLOAT_IS_CONSTEXPR 0 +#endif + +#endif // FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/decimal_to_binary.h b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/decimal_to_binary.h new file mode 100644 index 000000000..fec916f3a --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/decimal_to_binary.h @@ -0,0 +1,189 @@ +#ifndef FASTFLOAT_DECIMAL_TO_BINARY_H +#define FASTFLOAT_DECIMAL_TO_BINARY_H + +#include "float_common.h" +#include "fast_table.h" +#include +#include +#include +#include +#include +#include + +namespace fast_float { + +// This will compute or rather approximate w * 5**q and return a pair of 64-bit words approximating +// the result, with the "high" part corresponding to the most significant bits and the +// low part corresponding to the least significant bits. +// +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +value128 compute_product_approximation(int64_t q, uint64_t w) { + const int index = 2 * int(q - powers::smallest_power_of_five); + // For small values of q, e.g., q in [0,27], the answer is always exact because + // The line value128 firstproduct = full_multiplication(w, power_of_five_128[index]); + // gives the exact answer. + value128 firstproduct = full_multiplication(w, powers::power_of_five_128[index]); + static_assert((bit_precision >= 0) && (bit_precision <= 64), " precision should be in (0,64]"); + constexpr uint64_t precision_mask = (bit_precision < 64) ? + (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) + : uint64_t(0xFFFFFFFFFFFFFFFF); + if((firstproduct.high & precision_mask) == precision_mask) { // could further guard with (lower + w < lower) + // regarding the second product, we only need secondproduct.high, but our expectation is that the compiler will optimize this extra work away if needed. + value128 secondproduct = full_multiplication(w, powers::power_of_five_128[index + 1]); + firstproduct.low += secondproduct.high; + if(secondproduct.high > firstproduct.low) { + firstproduct.high++; + } + } + return firstproduct; +} + +namespace detail { +/** + * For q in (0,350), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * floor(p) + q + * where + * p = log(5**q)/log(2) = q * log(5)/log(2) + * + * For negative values of q in (-400,0), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * -ceil(p) + q + * where + * p = log(5**-q)/log(2) = -q * log(5)/log(2) + */ + constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { + return (((152170 + 65536) * q) >> 16) + 63; + } +} // namespace detail + +// create an adjusted mantissa, biased by the invalid power2 +// for significant digits already multiplied by 10 ** q. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 +adjusted_mantissa compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { + int hilz = int(w >> 63) ^ 1; + adjusted_mantissa answer; + answer.mantissa = w << hilz; + int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); + answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + invalid_am_bias); + return answer; +} + +// w * 10 ** q, without rounding the representation up. +// the power2 in the exponent will be adjusted by invalid_am_bias. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept { + int lz = leading_zeroes(w); + w <<= lz; + value128 product = compute_product_approximation(q, w); + return compute_error_scaled(q, product.high, lz); +} + +// w * 10 ** q +// The returned value should be a valid ieee64 number that simply need to be packed. +// However, in some very rare cases, the computation will fail. In such cases, we +// return an adjusted_mantissa with a negative power of 2: the caller should recompute +// in such cases. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { + adjusted_mantissa answer; + if ((w == 0) || (q < binary::smallest_power_of_ten())) { + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + if (q > binary::largest_power_of_ten()) { + // we want to get infinity: + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + // At this point in time q is in [powers::smallest_power_of_five, powers::largest_power_of_five]. + + // We want the most significant bit of i to be 1. Shift if needed. + int lz = leading_zeroes(w); + w <<= lz; + + // The required precision is binary::mantissa_explicit_bits() + 3 because + // 1. We need the implicit bit + // 2. We need an extra bit for rounding purposes + // 3. We might lose a bit due to the "upperbit" routine (result too small, requiring a shift) + + value128 product = compute_product_approximation(q, w); + // The computed 'product' is always sufficient. + // Mathematical proof: + // Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to appear) + // See script/mushtak_lemire.py + + // The "compute_product_approximation" function can be slightly slower than a branchless approach: + // value128 product = compute_product(q, w); + // but in practice, we can win big with the compute_product_approximation if its additional branch + // is easily predicted. Which is best is data specific. + int upperbit = int(product.high >> 63); + + answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); + + answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - binary::minimum_exponent()); + if (answer.power2 <= 0) { // we have a subnormal? + // Here have that answer.power2 <= 0 so -answer.power2 >= 0 + if(-answer.power2 + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure. + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + // next line is safe because -answer.power2 + 1 < 64 + answer.mantissa >>= -answer.power2 + 1; + // Thankfully, we can't have both "round-to-even" and subnormals because + // "round-to-even" only occurs for powers close to 0. + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + // There is a weird scenario where we don't have a subnormal but just. + // Suppose we start with 2.2250738585072013e-308, we end up + // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal + // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round + // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer + // subnormal, but we can only know this after rounding. + // So we only declare a subnormal if we are smaller than the threshold. + answer.power2 = (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) ? 0 : 1; + return answer; + } + + // usually, we round *up*, but if we fall right in between and and we have an + // even basis, we need to round down + // We are only concerned with the cases where 5**q fits in single 64-bit word. + if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && (q <= binary::max_exponent_round_to_even()) && + ((answer.mantissa & 3) == 1) ) { // we may fall between two floats! + // To be in-between two floats we need that in doing + // answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); + // ... we dropped out only zeroes. But if this happened, then we can go back!!! + if((answer.mantissa << (upperbit + 64 - binary::mantissa_explicit_bits() - 3)) == product.high) { + answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up + } + } + + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { + answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); + answer.power2++; // undo previous addition + } + + answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); + if (answer.power2 >= binary::infinite_power()) { // infinity + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + } + return answer; +} + +} // namespace fast_float + +#endif diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/digit_comparison.h b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/digit_comparison.h new file mode 100644 index 000000000..512a27f5a --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/digit_comparison.h @@ -0,0 +1,426 @@ +#ifndef FASTFLOAT_DIGIT_COMPARISON_H +#define FASTFLOAT_DIGIT_COMPARISON_H + +#include +#include +#include +#include + +#include "float_common.h" +#include "bigint.h" +#include "ascii_number.h" + +namespace fast_float { + +// 1e0 to 1e19 +constexpr static uint64_t powers_of_ten_uint64[] = { + 1UL, 10UL, 100UL, 1000UL, 10000UL, 100000UL, 1000000UL, 10000000UL, 100000000UL, + 1000000000UL, 10000000000UL, 100000000000UL, 1000000000000UL, 10000000000000UL, + 100000000000000UL, 1000000000000000UL, 10000000000000000UL, 100000000000000000UL, + 1000000000000000000UL, 10000000000000000000UL}; + +// calculate the exponent, in scientific notation, of the number. +// this algorithm is not even close to optimized, but it has no practical +// effect on performance: in order to have a faster algorithm, we'd need +// to slow down performance for faster algorithms, and this is still fast. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 +int32_t scientific_exponent(parsed_number_string_t & num) noexcept { + uint64_t mantissa = num.mantissa; + int32_t exponent = int32_t(num.exponent); + while (mantissa >= 10000) { + mantissa /= 10000; + exponent += 4; + } + while (mantissa >= 100) { + mantissa /= 100; + exponent += 2; + } + while (mantissa >= 10) { + mantissa /= 10; + exponent += 1; + } + return exponent; +} + +// this converts a native floating-point number to an extended-precision float. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +adjusted_mantissa to_extended(T value) noexcept { + using equiv_uint = typename binary_format::equiv_uint; + constexpr equiv_uint exponent_mask = binary_format::exponent_mask(); + constexpr equiv_uint mantissa_mask = binary_format::mantissa_mask(); + constexpr equiv_uint hidden_bit_mask = binary_format::hidden_bit_mask(); + + adjusted_mantissa am; + int32_t bias = binary_format::mantissa_explicit_bits() - binary_format::minimum_exponent(); + equiv_uint bits; +#if FASTFLOAT_HAS_BIT_CAST + bits = std::bit_cast(value); +#else + ::memcpy(&bits, &value, sizeof(T)); +#endif + if ((bits & exponent_mask) == 0) { + // denormal + am.power2 = 1 - bias; + am.mantissa = bits & mantissa_mask; + } else { + // normal + am.power2 = int32_t((bits & exponent_mask) >> binary_format::mantissa_explicit_bits()); + am.power2 -= bias; + am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; + } + + return am; +} + +// get the extended precision value of the halfway point between b and b+u. +// we are given a native float that represents b, so we need to adjust it +// halfway between b and b+u. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +adjusted_mantissa to_extended_halfway(T value) noexcept { + adjusted_mantissa am = to_extended(value); + am.mantissa <<= 1; + am.mantissa += 1; + am.power2 -= 1; + return am; +} + +// round an extended-precision float to the nearest machine float. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 +void round(adjusted_mantissa& am, callback cb) noexcept { + int32_t mantissa_shift = 64 - binary_format::mantissa_explicit_bits() - 1; + if (-am.power2 >= mantissa_shift) { + // have a denormal float + int32_t shift = -am.power2 + 1; + cb(am, std::min(shift, 64)); + // check for round-up: if rounding-nearest carried us to the hidden bit. + am.power2 = (am.mantissa < (uint64_t(1) << binary_format::mantissa_explicit_bits())) ? 0 : 1; + return; + } + + // have a normal float, use the default shift. + cb(am, mantissa_shift); + + // check for carry + if (am.mantissa >= (uint64_t(2) << binary_format::mantissa_explicit_bits())) { + am.mantissa = (uint64_t(1) << binary_format::mantissa_explicit_bits()); + am.power2++; + } + + // check for infinite: we could have carried to an infinite power + am.mantissa &= ~(uint64_t(1) << binary_format::mantissa_explicit_bits()); + if (am.power2 >= binary_format::infinite_power()) { + am.power2 = binary_format::infinite_power(); + am.mantissa = 0; + } +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 +void round_nearest_tie_even(adjusted_mantissa& am, int32_t shift, callback cb) noexcept { + const uint64_t mask + = (shift == 64) + ? UINT64_MAX + : (uint64_t(1) << shift) - 1; + const uint64_t halfway + = (shift == 0) + ? 0 + : uint64_t(1) << (shift - 1); + uint64_t truncated_bits = am.mantissa & mask; + bool is_above = truncated_bits > halfway; + bool is_halfway = truncated_bits == halfway; + + // shift digits into position + if (shift == 64) { + am.mantissa = 0; + } else { + am.mantissa >>= shift; + } + am.power2 += shift; + + bool is_odd = (am.mantissa & 1) == 1; + am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 +void round_down(adjusted_mantissa& am, int32_t shift) noexcept { + if (shift == 64) { + am.mantissa = 0; + } else { + am.mantissa >>= shift; + } + am.power2 += shift; +} +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +void skip_zeros(UC const * & first, UC const * last) noexcept { + uint64_t val; + while (!cpp20_and_in_constexpr() && std::distance(first, last) >= int_cmp_len()) { + ::memcpy(&val, first, sizeof(uint64_t)); + if (val != int_cmp_zeros()) { + break; + } + first += int_cmp_len(); + } + while (first != last) { + if (*first != UC('0')) { + break; + } + first++; + } +} + +// determine if any non-zero digits were truncated. +// all characters must be valid digits. +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +bool is_truncated(UC const * first, UC const * last) noexcept { + // do 8-bit optimizations, can just compare to 8 literal 0s. + uint64_t val; + while (!cpp20_and_in_constexpr() && std::distance(first, last) >= int_cmp_len()) { + ::memcpy(&val, first, sizeof(uint64_t)); + if (val != int_cmp_zeros()) { + return true; + } + first += int_cmp_len(); + } + while (first != last) { + if (*first != UC('0')) { + return true; + } + ++first; + } + return false; +} +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +bool is_truncated(span s) noexcept { + return is_truncated(s.ptr, s.ptr + s.len()); +} + + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +void parse_eight_digits(const UC*& p, limb& value, size_t& counter, size_t& count) noexcept { + value = value * 100000000 + parse_eight_digits_unrolled(p); + p += 8; + counter += 8; + count += 8; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 +void parse_one_digit(UC const *& p, limb& value, size_t& counter, size_t& count) noexcept { + value = value * 10 + limb(*p - UC('0')); + p++; + counter++; + count++; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +void add_native(bigint& big, limb power, limb value) noexcept { + big.mul(power); + big.add(value); +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +void round_up_bigint(bigint& big, size_t& count) noexcept { + // need to round-up the digits, but need to avoid rounding + // ....9999 to ...10000, which could cause a false halfway point. + add_native(big, 10, 1); + count++; +} + +// parse the significant digits into a big integer +template +inline FASTFLOAT_CONSTEXPR20 +void parse_mantissa(bigint& result, parsed_number_string_t& num, size_t max_digits, size_t& digits) noexcept { + // try to minimize the number of big integer and scalar multiplication. + // therefore, try to parse 8 digits at a time, and multiply by the largest + // scalar value (9 or 19 digits) for each step. + size_t counter = 0; + digits = 0; + limb value = 0; +#ifdef FASTFLOAT_64BIT_LIMB + size_t step = 19; +#else + size_t step = 9; +#endif + + // process all integer digits. + UC const * p = num.integer.ptr; + UC const * pend = p + num.integer.len(); + skip_zeros(p, pend); + // process all digits, in increments of step per loop + while (p != pend) { + while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { + parse_eight_digits(p, value, counter, digits); + } + while (counter < step && p != pend && digits < max_digits) { + parse_one_digit(p, value, counter, digits); + } + if (digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + add_native(result, limb(powers_of_ten_uint64[counter]), value); + bool truncated = is_truncated(p, pend); + if (num.fraction.ptr != nullptr) { + truncated |= is_truncated(num.fraction); + } + if (truncated) { + round_up_bigint(result, digits); + } + return; + } else { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + + // add our fraction digits, if they're available. + if (num.fraction.ptr != nullptr) { + p = num.fraction.ptr; + pend = p + num.fraction.len(); + if (digits == 0) { + skip_zeros(p, pend); + } + // process all digits, in increments of step per loop + while (p != pend) { + while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { + parse_eight_digits(p, value, counter, digits); + } + while (counter < step && p != pend && digits < max_digits) { + parse_one_digit(p, value, counter, digits); + } + if (digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + add_native(result, limb(powers_of_ten_uint64[counter]), value); + bool truncated = is_truncated(p, pend); + if (truncated) { + round_up_bigint(result, digits); + } + return; + } else { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + } + + if (counter != 0) { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + } +} + +template +inline FASTFLOAT_CONSTEXPR20 +adjusted_mantissa positive_digit_comp(bigint& bigmant, int32_t exponent) noexcept { + FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); + adjusted_mantissa answer; + bool truncated; + answer.mantissa = bigmant.hi64(truncated); + int bias = binary_format::mantissa_explicit_bits() - binary_format::minimum_exponent(); + answer.power2 = bigmant.bit_length() - 64 + bias; + + round(answer, [truncated](adjusted_mantissa& a, int32_t shift) { + round_nearest_tie_even(a, shift, [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { + return is_above || (is_halfway && truncated) || (is_odd && is_halfway); + }); + }); + + return answer; +} + +// the scaling here is quite simple: we have, for the real digits `m * 10^e`, +// and for the theoretical digits `n * 2^f`. Since `e` is always negative, +// to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`. +// we then need to scale by `2^(f- e)`, and then the two significant digits +// are of the same magnitude. +template +inline FASTFLOAT_CONSTEXPR20 +adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa am, int32_t exponent) noexcept { + bigint& real_digits = bigmant; + int32_t real_exp = exponent; + + // get the value of `b`, rounded down, and get a bigint representation of b+h + adjusted_mantissa am_b = am; + // gcc7 buf: use a lambda to remove the noexcept qualifier bug with -Wnoexcept-type. + round(am_b, [](adjusted_mantissa&a, int32_t shift) { round_down(a, shift); }); + T b; + to_float(false, am_b, b); + adjusted_mantissa theor = to_extended_halfway(b); + bigint theor_digits(theor.mantissa); + int32_t theor_exp = theor.power2; + + // scale real digits and theor digits to be same power. + int32_t pow2_exp = theor_exp - real_exp; + uint32_t pow5_exp = uint32_t(-real_exp); + if (pow5_exp != 0) { + FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp)); + } + if (pow2_exp > 0) { + FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp))); + } else if (pow2_exp < 0) { + FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp))); + } + + // compare digits, and use it to director rounding + int ord = real_digits.compare(theor_digits); + adjusted_mantissa answer = am; + round(answer, [ord](adjusted_mantissa& a, int32_t shift) { + round_nearest_tie_even(a, shift, [ord](bool is_odd, bool _, bool __) -> bool { + (void)_; // not needed, since we've done our comparison + (void)__; // not needed, since we've done our comparison + if (ord > 0) { + return true; + } else if (ord < 0) { + return false; + } else { + return is_odd; + } + }); + }); + + return answer; +} + +// parse the significant digits as a big integer to unambiguously round the +// the significant digits. here, we are trying to determine how to round +// an extended float representation close to `b+h`, halfway between `b` +// (the float rounded-down) and `b+u`, the next positive float. this +// algorithm is always correct, and uses one of two approaches. when +// the exponent is positive relative to the significant digits (such as +// 1234), we create a big-integer representation, get the high 64-bits, +// determine if any lower bits are truncated, and use that to direct +// rounding. in case of a negative exponent relative to the significant +// digits (such as 1.2345), we create a theoretical representation of +// `b` as a big-integer type, scaled to the same binary exponent as +// the actual digits. we then compare the big integer representations +// of both, and use that to direct rounding. +template +inline FASTFLOAT_CONSTEXPR20 +adjusted_mantissa digit_comp(parsed_number_string_t& num, adjusted_mantissa am) noexcept { + // remove the invalid exponent bias + am.power2 -= invalid_am_bias; + + int32_t sci_exp = scientific_exponent(num); + size_t max_digits = binary_format::max_digits(); + size_t digits = 0; + bigint bigmant; + parse_mantissa(bigmant, num, max_digits, digits); + // can't underflow, since digits is at most max_digits. + int32_t exponent = sci_exp + 1 - int32_t(digits); + if (exponent >= 0) { + return positive_digit_comp(bigmant, exponent); + } else { + return negative_digit_comp(bigmant, am, exponent); + } +} + +} // namespace fast_float + +#endif diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/fast_float.h b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/fast_float.h new file mode 100644 index 000000000..04efa877e --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/fast_float.h @@ -0,0 +1,42 @@ + +#ifndef FASTFLOAT_FAST_FLOAT_H +#define FASTFLOAT_FAST_FLOAT_H + +#include "float_common.h" + +namespace fast_float { +/** + * This function parses the character sequence [first,last) for a number. It parses floating-point numbers expecting + * a locale-indepent format equivalent to what is used by std::strtod in the default ("C") locale. + * The resulting floating-point value is the closest floating-point values (using either float or double), + * using the "round to even" convention for values that would otherwise fall right in-between two values. + * That is, we provide exact parsing according to the IEEE standard. + * + * Given a successful parse, the pointer (`ptr`) in the returned value is set to point right after the + * parsed number, and the `value` referenced is set to the parsed value. In case of error, the returned + * `ec` contains a representative error, otherwise the default (`std::errc()`) value is stored. + * + * The implementation does not throw and does not allocate memory (e.g., with `new` or `malloc`). + * + * Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of + * the type `fast_float::chars_format`. It is a bitset value: we check whether + * `fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set + * to determine whether we allow the fixed point and scientific notation respectively. + * The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`. + */ +template +FASTFLOAT_CONSTEXPR20 +from_chars_result_t from_chars(UC const * first, UC const * last, + T &value, chars_format fmt = chars_format::general) noexcept; + +/** + * Like from_chars, but accepts an `options` argument to govern number parsing. + */ +template +FASTFLOAT_CONSTEXPR20 +from_chars_result_t from_chars_advanced(UC const * first, UC const * last, + T &value, parse_options_t options) noexcept; + +} // namespace fast_float +#include "parse_number.h" +#endif // FASTFLOAT_FAST_FLOAT_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/fast_table.h b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/fast_table.h new file mode 100644 index 000000000..d8dc56905 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/fast_table.h @@ -0,0 +1,700 @@ +#ifndef FASTFLOAT_FAST_TABLE_H +#define FASTFLOAT_FAST_TABLE_H + +#include + +namespace fast_float { + +/** + * When mapping numbers from decimal to binary, + * we go from w * 10^q to m * 2^p but we have + * 10^q = 5^q * 2^q, so effectively + * we are trying to match + * w * 2^q * 5^q to m * 2^p. Thus the powers of two + * are not a concern since they can be represented + * exactly using the binary notation, only the powers of five + * affect the binary significand. + */ + +/** + * The smallest non-zero float (binary64) is 2^-1074. + * We take as input numbers of the form w x 10^q where w < 2^64. + * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. + * However, we have that + * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^-1074. + * Thus it is possible for a number of the form w * 10^-342 where + * w is a 64-bit value to be a non-zero floating-point number. + ********* + * Any number of form w * 10^309 where w>= 1 is going to be + * infinite in binary64 so we never need to worry about powers + * of 5 greater than 308. + */ +template +struct powers_template { + +constexpr static int smallest_power_of_five = binary_format::smallest_power_of_ten(); +constexpr static int largest_power_of_five = binary_format::largest_power_of_ten(); +constexpr static int number_of_entries = 2 * (largest_power_of_five - smallest_power_of_five + 1); +// Powers of five from 5^-342 all the way to 5^308 rounded toward one. +constexpr static uint64_t power_of_five_128[number_of_entries] = { + 0xeef453d6923bd65a,0x113faa2906a13b3f, + 0x9558b4661b6565f8,0x4ac7ca59a424c507, + 0xbaaee17fa23ebf76,0x5d79bcf00d2df649, + 0xe95a99df8ace6f53,0xf4d82c2c107973dc, + 0x91d8a02bb6c10594,0x79071b9b8a4be869, + 0xb64ec836a47146f9,0x9748e2826cdee284, + 0xe3e27a444d8d98b7,0xfd1b1b2308169b25, + 0x8e6d8c6ab0787f72,0xfe30f0f5e50e20f7, + 0xb208ef855c969f4f,0xbdbd2d335e51a935, + 0xde8b2b66b3bc4723,0xad2c788035e61382, + 0x8b16fb203055ac76,0x4c3bcb5021afcc31, + 0xaddcb9e83c6b1793,0xdf4abe242a1bbf3d, + 0xd953e8624b85dd78,0xd71d6dad34a2af0d, + 0x87d4713d6f33aa6b,0x8672648c40e5ad68, + 0xa9c98d8ccb009506,0x680efdaf511f18c2, + 0xd43bf0effdc0ba48,0x212bd1b2566def2, + 0x84a57695fe98746d,0x14bb630f7604b57, + 0xa5ced43b7e3e9188,0x419ea3bd35385e2d, + 0xcf42894a5dce35ea,0x52064cac828675b9, + 0x818995ce7aa0e1b2,0x7343efebd1940993, + 0xa1ebfb4219491a1f,0x1014ebe6c5f90bf8, + 0xca66fa129f9b60a6,0xd41a26e077774ef6, + 0xfd00b897478238d0,0x8920b098955522b4, + 0x9e20735e8cb16382,0x55b46e5f5d5535b0, + 0xc5a890362fddbc62,0xeb2189f734aa831d, + 0xf712b443bbd52b7b,0xa5e9ec7501d523e4, + 0x9a6bb0aa55653b2d,0x47b233c92125366e, + 0xc1069cd4eabe89f8,0x999ec0bb696e840a, + 0xf148440a256e2c76,0xc00670ea43ca250d, + 0x96cd2a865764dbca,0x380406926a5e5728, + 0xbc807527ed3e12bc,0xc605083704f5ecf2, + 0xeba09271e88d976b,0xf7864a44c633682e, + 0x93445b8731587ea3,0x7ab3ee6afbe0211d, + 0xb8157268fdae9e4c,0x5960ea05bad82964, + 0xe61acf033d1a45df,0x6fb92487298e33bd, + 0x8fd0c16206306bab,0xa5d3b6d479f8e056, + 0xb3c4f1ba87bc8696,0x8f48a4899877186c, + 0xe0b62e2929aba83c,0x331acdabfe94de87, + 0x8c71dcd9ba0b4925,0x9ff0c08b7f1d0b14, + 0xaf8e5410288e1b6f,0x7ecf0ae5ee44dd9, + 0xdb71e91432b1a24a,0xc9e82cd9f69d6150, + 0x892731ac9faf056e,0xbe311c083a225cd2, + 0xab70fe17c79ac6ca,0x6dbd630a48aaf406, + 0xd64d3d9db981787d,0x92cbbccdad5b108, + 0x85f0468293f0eb4e,0x25bbf56008c58ea5, + 0xa76c582338ed2621,0xaf2af2b80af6f24e, + 0xd1476e2c07286faa,0x1af5af660db4aee1, + 0x82cca4db847945ca,0x50d98d9fc890ed4d, + 0xa37fce126597973c,0xe50ff107bab528a0, + 0xcc5fc196fefd7d0c,0x1e53ed49a96272c8, + 0xff77b1fcbebcdc4f,0x25e8e89c13bb0f7a, + 0x9faacf3df73609b1,0x77b191618c54e9ac, + 0xc795830d75038c1d,0xd59df5b9ef6a2417, + 0xf97ae3d0d2446f25,0x4b0573286b44ad1d, + 0x9becce62836ac577,0x4ee367f9430aec32, + 0xc2e801fb244576d5,0x229c41f793cda73f, + 0xf3a20279ed56d48a,0x6b43527578c1110f, + 0x9845418c345644d6,0x830a13896b78aaa9, + 0xbe5691ef416bd60c,0x23cc986bc656d553, + 0xedec366b11c6cb8f,0x2cbfbe86b7ec8aa8, + 0x94b3a202eb1c3f39,0x7bf7d71432f3d6a9, + 0xb9e08a83a5e34f07,0xdaf5ccd93fb0cc53, + 0xe858ad248f5c22c9,0xd1b3400f8f9cff68, + 0x91376c36d99995be,0x23100809b9c21fa1, + 0xb58547448ffffb2d,0xabd40a0c2832a78a, + 0xe2e69915b3fff9f9,0x16c90c8f323f516c, + 0x8dd01fad907ffc3b,0xae3da7d97f6792e3, + 0xb1442798f49ffb4a,0x99cd11cfdf41779c, + 0xdd95317f31c7fa1d,0x40405643d711d583, + 0x8a7d3eef7f1cfc52,0x482835ea666b2572, + 0xad1c8eab5ee43b66,0xda3243650005eecf, + 0xd863b256369d4a40,0x90bed43e40076a82, + 0x873e4f75e2224e68,0x5a7744a6e804a291, + 0xa90de3535aaae202,0x711515d0a205cb36, + 0xd3515c2831559a83,0xd5a5b44ca873e03, + 0x8412d9991ed58091,0xe858790afe9486c2, + 0xa5178fff668ae0b6,0x626e974dbe39a872, + 0xce5d73ff402d98e3,0xfb0a3d212dc8128f, + 0x80fa687f881c7f8e,0x7ce66634bc9d0b99, + 0xa139029f6a239f72,0x1c1fffc1ebc44e80, + 0xc987434744ac874e,0xa327ffb266b56220, + 0xfbe9141915d7a922,0x4bf1ff9f0062baa8, + 0x9d71ac8fada6c9b5,0x6f773fc3603db4a9, + 0xc4ce17b399107c22,0xcb550fb4384d21d3, + 0xf6019da07f549b2b,0x7e2a53a146606a48, + 0x99c102844f94e0fb,0x2eda7444cbfc426d, + 0xc0314325637a1939,0xfa911155fefb5308, + 0xf03d93eebc589f88,0x793555ab7eba27ca, + 0x96267c7535b763b5,0x4bc1558b2f3458de, + 0xbbb01b9283253ca2,0x9eb1aaedfb016f16, + 0xea9c227723ee8bcb,0x465e15a979c1cadc, + 0x92a1958a7675175f,0xbfacd89ec191ec9, + 0xb749faed14125d36,0xcef980ec671f667b, + 0xe51c79a85916f484,0x82b7e12780e7401a, + 0x8f31cc0937ae58d2,0xd1b2ecb8b0908810, + 0xb2fe3f0b8599ef07,0x861fa7e6dcb4aa15, + 0xdfbdcece67006ac9,0x67a791e093e1d49a, + 0x8bd6a141006042bd,0xe0c8bb2c5c6d24e0, + 0xaecc49914078536d,0x58fae9f773886e18, + 0xda7f5bf590966848,0xaf39a475506a899e, + 0x888f99797a5e012d,0x6d8406c952429603, + 0xaab37fd7d8f58178,0xc8e5087ba6d33b83, + 0xd5605fcdcf32e1d6,0xfb1e4a9a90880a64, + 0x855c3be0a17fcd26,0x5cf2eea09a55067f, + 0xa6b34ad8c9dfc06f,0xf42faa48c0ea481e, + 0xd0601d8efc57b08b,0xf13b94daf124da26, + 0x823c12795db6ce57,0x76c53d08d6b70858, + 0xa2cb1717b52481ed,0x54768c4b0c64ca6e, + 0xcb7ddcdda26da268,0xa9942f5dcf7dfd09, + 0xfe5d54150b090b02,0xd3f93b35435d7c4c, + 0x9efa548d26e5a6e1,0xc47bc5014a1a6daf, + 0xc6b8e9b0709f109a,0x359ab6419ca1091b, + 0xf867241c8cc6d4c0,0xc30163d203c94b62, + 0x9b407691d7fc44f8,0x79e0de63425dcf1d, + 0xc21094364dfb5636,0x985915fc12f542e4, + 0xf294b943e17a2bc4,0x3e6f5b7b17b2939d, + 0x979cf3ca6cec5b5a,0xa705992ceecf9c42, + 0xbd8430bd08277231,0x50c6ff782a838353, + 0xece53cec4a314ebd,0xa4f8bf5635246428, + 0x940f4613ae5ed136,0x871b7795e136be99, + 0xb913179899f68584,0x28e2557b59846e3f, + 0xe757dd7ec07426e5,0x331aeada2fe589cf, + 0x9096ea6f3848984f,0x3ff0d2c85def7621, + 0xb4bca50b065abe63,0xfed077a756b53a9, + 0xe1ebce4dc7f16dfb,0xd3e8495912c62894, + 0x8d3360f09cf6e4bd,0x64712dd7abbbd95c, + 0xb080392cc4349dec,0xbd8d794d96aacfb3, + 0xdca04777f541c567,0xecf0d7a0fc5583a0, + 0x89e42caaf9491b60,0xf41686c49db57244, + 0xac5d37d5b79b6239,0x311c2875c522ced5, + 0xd77485cb25823ac7,0x7d633293366b828b, + 0x86a8d39ef77164bc,0xae5dff9c02033197, + 0xa8530886b54dbdeb,0xd9f57f830283fdfc, + 0xd267caa862a12d66,0xd072df63c324fd7b, + 0x8380dea93da4bc60,0x4247cb9e59f71e6d, + 0xa46116538d0deb78,0x52d9be85f074e608, + 0xcd795be870516656,0x67902e276c921f8b, + 0x806bd9714632dff6,0xba1cd8a3db53b6, + 0xa086cfcd97bf97f3,0x80e8a40eccd228a4, + 0xc8a883c0fdaf7df0,0x6122cd128006b2cd, + 0xfad2a4b13d1b5d6c,0x796b805720085f81, + 0x9cc3a6eec6311a63,0xcbe3303674053bb0, + 0xc3f490aa77bd60fc,0xbedbfc4411068a9c, + 0xf4f1b4d515acb93b,0xee92fb5515482d44, + 0x991711052d8bf3c5,0x751bdd152d4d1c4a, + 0xbf5cd54678eef0b6,0xd262d45a78a0635d, + 0xef340a98172aace4,0x86fb897116c87c34, + 0x9580869f0e7aac0e,0xd45d35e6ae3d4da0, + 0xbae0a846d2195712,0x8974836059cca109, + 0xe998d258869facd7,0x2bd1a438703fc94b, + 0x91ff83775423cc06,0x7b6306a34627ddcf, + 0xb67f6455292cbf08,0x1a3bc84c17b1d542, + 0xe41f3d6a7377eeca,0x20caba5f1d9e4a93, + 0x8e938662882af53e,0x547eb47b7282ee9c, + 0xb23867fb2a35b28d,0xe99e619a4f23aa43, + 0xdec681f9f4c31f31,0x6405fa00e2ec94d4, + 0x8b3c113c38f9f37e,0xde83bc408dd3dd04, + 0xae0b158b4738705e,0x9624ab50b148d445, + 0xd98ddaee19068c76,0x3badd624dd9b0957, + 0x87f8a8d4cfa417c9,0xe54ca5d70a80e5d6, + 0xa9f6d30a038d1dbc,0x5e9fcf4ccd211f4c, + 0xd47487cc8470652b,0x7647c3200069671f, + 0x84c8d4dfd2c63f3b,0x29ecd9f40041e073, + 0xa5fb0a17c777cf09,0xf468107100525890, + 0xcf79cc9db955c2cc,0x7182148d4066eeb4, + 0x81ac1fe293d599bf,0xc6f14cd848405530, + 0xa21727db38cb002f,0xb8ada00e5a506a7c, + 0xca9cf1d206fdc03b,0xa6d90811f0e4851c, + 0xfd442e4688bd304a,0x908f4a166d1da663, + 0x9e4a9cec15763e2e,0x9a598e4e043287fe, + 0xc5dd44271ad3cdba,0x40eff1e1853f29fd, + 0xf7549530e188c128,0xd12bee59e68ef47c, + 0x9a94dd3e8cf578b9,0x82bb74f8301958ce, + 0xc13a148e3032d6e7,0xe36a52363c1faf01, + 0xf18899b1bc3f8ca1,0xdc44e6c3cb279ac1, + 0x96f5600f15a7b7e5,0x29ab103a5ef8c0b9, + 0xbcb2b812db11a5de,0x7415d448f6b6f0e7, + 0xebdf661791d60f56,0x111b495b3464ad21, + 0x936b9fcebb25c995,0xcab10dd900beec34, + 0xb84687c269ef3bfb,0x3d5d514f40eea742, + 0xe65829b3046b0afa,0xcb4a5a3112a5112, + 0x8ff71a0fe2c2e6dc,0x47f0e785eaba72ab, + 0xb3f4e093db73a093,0x59ed216765690f56, + 0xe0f218b8d25088b8,0x306869c13ec3532c, + 0x8c974f7383725573,0x1e414218c73a13fb, + 0xafbd2350644eeacf,0xe5d1929ef90898fa, + 0xdbac6c247d62a583,0xdf45f746b74abf39, + 0x894bc396ce5da772,0x6b8bba8c328eb783, + 0xab9eb47c81f5114f,0x66ea92f3f326564, + 0xd686619ba27255a2,0xc80a537b0efefebd, + 0x8613fd0145877585,0xbd06742ce95f5f36, + 0xa798fc4196e952e7,0x2c48113823b73704, + 0xd17f3b51fca3a7a0,0xf75a15862ca504c5, + 0x82ef85133de648c4,0x9a984d73dbe722fb, + 0xa3ab66580d5fdaf5,0xc13e60d0d2e0ebba, + 0xcc963fee10b7d1b3,0x318df905079926a8, + 0xffbbcfe994e5c61f,0xfdf17746497f7052, + 0x9fd561f1fd0f9bd3,0xfeb6ea8bedefa633, + 0xc7caba6e7c5382c8,0xfe64a52ee96b8fc0, + 0xf9bd690a1b68637b,0x3dfdce7aa3c673b0, + 0x9c1661a651213e2d,0x6bea10ca65c084e, + 0xc31bfa0fe5698db8,0x486e494fcff30a62, + 0xf3e2f893dec3f126,0x5a89dba3c3efccfa, + 0x986ddb5c6b3a76b7,0xf89629465a75e01c, + 0xbe89523386091465,0xf6bbb397f1135823, + 0xee2ba6c0678b597f,0x746aa07ded582e2c, + 0x94db483840b717ef,0xa8c2a44eb4571cdc, + 0xba121a4650e4ddeb,0x92f34d62616ce413, + 0xe896a0d7e51e1566,0x77b020baf9c81d17, + 0x915e2486ef32cd60,0xace1474dc1d122e, + 0xb5b5ada8aaff80b8,0xd819992132456ba, + 0xe3231912d5bf60e6,0x10e1fff697ed6c69, + 0x8df5efabc5979c8f,0xca8d3ffa1ef463c1, + 0xb1736b96b6fd83b3,0xbd308ff8a6b17cb2, + 0xddd0467c64bce4a0,0xac7cb3f6d05ddbde, + 0x8aa22c0dbef60ee4,0x6bcdf07a423aa96b, + 0xad4ab7112eb3929d,0x86c16c98d2c953c6, + 0xd89d64d57a607744,0xe871c7bf077ba8b7, + 0x87625f056c7c4a8b,0x11471cd764ad4972, + 0xa93af6c6c79b5d2d,0xd598e40d3dd89bcf, + 0xd389b47879823479,0x4aff1d108d4ec2c3, + 0x843610cb4bf160cb,0xcedf722a585139ba, + 0xa54394fe1eedb8fe,0xc2974eb4ee658828, + 0xce947a3da6a9273e,0x733d226229feea32, + 0x811ccc668829b887,0x806357d5a3f525f, + 0xa163ff802a3426a8,0xca07c2dcb0cf26f7, + 0xc9bcff6034c13052,0xfc89b393dd02f0b5, + 0xfc2c3f3841f17c67,0xbbac2078d443ace2, + 0x9d9ba7832936edc0,0xd54b944b84aa4c0d, + 0xc5029163f384a931,0xa9e795e65d4df11, + 0xf64335bcf065d37d,0x4d4617b5ff4a16d5, + 0x99ea0196163fa42e,0x504bced1bf8e4e45, + 0xc06481fb9bcf8d39,0xe45ec2862f71e1d6, + 0xf07da27a82c37088,0x5d767327bb4e5a4c, + 0x964e858c91ba2655,0x3a6a07f8d510f86f, + 0xbbe226efb628afea,0x890489f70a55368b, + 0xeadab0aba3b2dbe5,0x2b45ac74ccea842e, + 0x92c8ae6b464fc96f,0x3b0b8bc90012929d, + 0xb77ada0617e3bbcb,0x9ce6ebb40173744, + 0xe55990879ddcaabd,0xcc420a6a101d0515, + 0x8f57fa54c2a9eab6,0x9fa946824a12232d, + 0xb32df8e9f3546564,0x47939822dc96abf9, + 0xdff9772470297ebd,0x59787e2b93bc56f7, + 0x8bfbea76c619ef36,0x57eb4edb3c55b65a, + 0xaefae51477a06b03,0xede622920b6b23f1, + 0xdab99e59958885c4,0xe95fab368e45eced, + 0x88b402f7fd75539b,0x11dbcb0218ebb414, + 0xaae103b5fcd2a881,0xd652bdc29f26a119, + 0xd59944a37c0752a2,0x4be76d3346f0495f, + 0x857fcae62d8493a5,0x6f70a4400c562ddb, + 0xa6dfbd9fb8e5b88e,0xcb4ccd500f6bb952, + 0xd097ad07a71f26b2,0x7e2000a41346a7a7, + 0x825ecc24c873782f,0x8ed400668c0c28c8, + 0xa2f67f2dfa90563b,0x728900802f0f32fa, + 0xcbb41ef979346bca,0x4f2b40a03ad2ffb9, + 0xfea126b7d78186bc,0xe2f610c84987bfa8, + 0x9f24b832e6b0f436,0xdd9ca7d2df4d7c9, + 0xc6ede63fa05d3143,0x91503d1c79720dbb, + 0xf8a95fcf88747d94,0x75a44c6397ce912a, + 0x9b69dbe1b548ce7c,0xc986afbe3ee11aba, + 0xc24452da229b021b,0xfbe85badce996168, + 0xf2d56790ab41c2a2,0xfae27299423fb9c3, + 0x97c560ba6b0919a5,0xdccd879fc967d41a, + 0xbdb6b8e905cb600f,0x5400e987bbc1c920, + 0xed246723473e3813,0x290123e9aab23b68, + 0x9436c0760c86e30b,0xf9a0b6720aaf6521, + 0xb94470938fa89bce,0xf808e40e8d5b3e69, + 0xe7958cb87392c2c2,0xb60b1d1230b20e04, + 0x90bd77f3483bb9b9,0xb1c6f22b5e6f48c2, + 0xb4ecd5f01a4aa828,0x1e38aeb6360b1af3, + 0xe2280b6c20dd5232,0x25c6da63c38de1b0, + 0x8d590723948a535f,0x579c487e5a38ad0e, + 0xb0af48ec79ace837,0x2d835a9df0c6d851, + 0xdcdb1b2798182244,0xf8e431456cf88e65, + 0x8a08f0f8bf0f156b,0x1b8e9ecb641b58ff, + 0xac8b2d36eed2dac5,0xe272467e3d222f3f, + 0xd7adf884aa879177,0x5b0ed81dcc6abb0f, + 0x86ccbb52ea94baea,0x98e947129fc2b4e9, + 0xa87fea27a539e9a5,0x3f2398d747b36224, + 0xd29fe4b18e88640e,0x8eec7f0d19a03aad, + 0x83a3eeeef9153e89,0x1953cf68300424ac, + 0xa48ceaaab75a8e2b,0x5fa8c3423c052dd7, + 0xcdb02555653131b6,0x3792f412cb06794d, + 0x808e17555f3ebf11,0xe2bbd88bbee40bd0, + 0xa0b19d2ab70e6ed6,0x5b6aceaeae9d0ec4, + 0xc8de047564d20a8b,0xf245825a5a445275, + 0xfb158592be068d2e,0xeed6e2f0f0d56712, + 0x9ced737bb6c4183d,0x55464dd69685606b, + 0xc428d05aa4751e4c,0xaa97e14c3c26b886, + 0xf53304714d9265df,0xd53dd99f4b3066a8, + 0x993fe2c6d07b7fab,0xe546a8038efe4029, + 0xbf8fdb78849a5f96,0xde98520472bdd033, + 0xef73d256a5c0f77c,0x963e66858f6d4440, + 0x95a8637627989aad,0xdde7001379a44aa8, + 0xbb127c53b17ec159,0x5560c018580d5d52, + 0xe9d71b689dde71af,0xaab8f01e6e10b4a6, + 0x9226712162ab070d,0xcab3961304ca70e8, + 0xb6b00d69bb55c8d1,0x3d607b97c5fd0d22, + 0xe45c10c42a2b3b05,0x8cb89a7db77c506a, + 0x8eb98a7a9a5b04e3,0x77f3608e92adb242, + 0xb267ed1940f1c61c,0x55f038b237591ed3, + 0xdf01e85f912e37a3,0x6b6c46dec52f6688, + 0x8b61313bbabce2c6,0x2323ac4b3b3da015, + 0xae397d8aa96c1b77,0xabec975e0a0d081a, + 0xd9c7dced53c72255,0x96e7bd358c904a21, + 0x881cea14545c7575,0x7e50d64177da2e54, + 0xaa242499697392d2,0xdde50bd1d5d0b9e9, + 0xd4ad2dbfc3d07787,0x955e4ec64b44e864, + 0x84ec3c97da624ab4,0xbd5af13bef0b113e, + 0xa6274bbdd0fadd61,0xecb1ad8aeacdd58e, + 0xcfb11ead453994ba,0x67de18eda5814af2, + 0x81ceb32c4b43fcf4,0x80eacf948770ced7, + 0xa2425ff75e14fc31,0xa1258379a94d028d, + 0xcad2f7f5359a3b3e,0x96ee45813a04330, + 0xfd87b5f28300ca0d,0x8bca9d6e188853fc, + 0x9e74d1b791e07e48,0x775ea264cf55347e, + 0xc612062576589dda,0x95364afe032a819e, + 0xf79687aed3eec551,0x3a83ddbd83f52205, + 0x9abe14cd44753b52,0xc4926a9672793543, + 0xc16d9a0095928a27,0x75b7053c0f178294, + 0xf1c90080baf72cb1,0x5324c68b12dd6339, + 0x971da05074da7bee,0xd3f6fc16ebca5e04, + 0xbce5086492111aea,0x88f4bb1ca6bcf585, + 0xec1e4a7db69561a5,0x2b31e9e3d06c32e6, + 0x9392ee8e921d5d07,0x3aff322e62439fd0, + 0xb877aa3236a4b449,0x9befeb9fad487c3, + 0xe69594bec44de15b,0x4c2ebe687989a9b4, + 0x901d7cf73ab0acd9,0xf9d37014bf60a11, + 0xb424dc35095cd80f,0x538484c19ef38c95, + 0xe12e13424bb40e13,0x2865a5f206b06fba, + 0x8cbccc096f5088cb,0xf93f87b7442e45d4, + 0xafebff0bcb24aafe,0xf78f69a51539d749, + 0xdbe6fecebdedd5be,0xb573440e5a884d1c, + 0x89705f4136b4a597,0x31680a88f8953031, + 0xabcc77118461cefc,0xfdc20d2b36ba7c3e, + 0xd6bf94d5e57a42bc,0x3d32907604691b4d, + 0x8637bd05af6c69b5,0xa63f9a49c2c1b110, + 0xa7c5ac471b478423,0xfcf80dc33721d54, + 0xd1b71758e219652b,0xd3c36113404ea4a9, + 0x83126e978d4fdf3b,0x645a1cac083126ea, + 0xa3d70a3d70a3d70a,0x3d70a3d70a3d70a4, + 0xcccccccccccccccc,0xcccccccccccccccd, + 0x8000000000000000,0x0, + 0xa000000000000000,0x0, + 0xc800000000000000,0x0, + 0xfa00000000000000,0x0, + 0x9c40000000000000,0x0, + 0xc350000000000000,0x0, + 0xf424000000000000,0x0, + 0x9896800000000000,0x0, + 0xbebc200000000000,0x0, + 0xee6b280000000000,0x0, + 0x9502f90000000000,0x0, + 0xba43b74000000000,0x0, + 0xe8d4a51000000000,0x0, + 0x9184e72a00000000,0x0, + 0xb5e620f480000000,0x0, + 0xe35fa931a0000000,0x0, + 0x8e1bc9bf04000000,0x0, + 0xb1a2bc2ec5000000,0x0, + 0xde0b6b3a76400000,0x0, + 0x8ac7230489e80000,0x0, + 0xad78ebc5ac620000,0x0, + 0xd8d726b7177a8000,0x0, + 0x878678326eac9000,0x0, + 0xa968163f0a57b400,0x0, + 0xd3c21bcecceda100,0x0, + 0x84595161401484a0,0x0, + 0xa56fa5b99019a5c8,0x0, + 0xcecb8f27f4200f3a,0x0, + 0x813f3978f8940984,0x4000000000000000, + 0xa18f07d736b90be5,0x5000000000000000, + 0xc9f2c9cd04674ede,0xa400000000000000, + 0xfc6f7c4045812296,0x4d00000000000000, + 0x9dc5ada82b70b59d,0xf020000000000000, + 0xc5371912364ce305,0x6c28000000000000, + 0xf684df56c3e01bc6,0xc732000000000000, + 0x9a130b963a6c115c,0x3c7f400000000000, + 0xc097ce7bc90715b3,0x4b9f100000000000, + 0xf0bdc21abb48db20,0x1e86d40000000000, + 0x96769950b50d88f4,0x1314448000000000, + 0xbc143fa4e250eb31,0x17d955a000000000, + 0xeb194f8e1ae525fd,0x5dcfab0800000000, + 0x92efd1b8d0cf37be,0x5aa1cae500000000, + 0xb7abc627050305ad,0xf14a3d9e40000000, + 0xe596b7b0c643c719,0x6d9ccd05d0000000, + 0x8f7e32ce7bea5c6f,0xe4820023a2000000, + 0xb35dbf821ae4f38b,0xdda2802c8a800000, + 0xe0352f62a19e306e,0xd50b2037ad200000, + 0x8c213d9da502de45,0x4526f422cc340000, + 0xaf298d050e4395d6,0x9670b12b7f410000, + 0xdaf3f04651d47b4c,0x3c0cdd765f114000, + 0x88d8762bf324cd0f,0xa5880a69fb6ac800, + 0xab0e93b6efee0053,0x8eea0d047a457a00, + 0xd5d238a4abe98068,0x72a4904598d6d880, + 0x85a36366eb71f041,0x47a6da2b7f864750, + 0xa70c3c40a64e6c51,0x999090b65f67d924, + 0xd0cf4b50cfe20765,0xfff4b4e3f741cf6d, + 0x82818f1281ed449f,0xbff8f10e7a8921a4, + 0xa321f2d7226895c7,0xaff72d52192b6a0d, + 0xcbea6f8ceb02bb39,0x9bf4f8a69f764490, + 0xfee50b7025c36a08,0x2f236d04753d5b4, + 0x9f4f2726179a2245,0x1d762422c946590, + 0xc722f0ef9d80aad6,0x424d3ad2b7b97ef5, + 0xf8ebad2b84e0d58b,0xd2e0898765a7deb2, + 0x9b934c3b330c8577,0x63cc55f49f88eb2f, + 0xc2781f49ffcfa6d5,0x3cbf6b71c76b25fb, + 0xf316271c7fc3908a,0x8bef464e3945ef7a, + 0x97edd871cfda3a56,0x97758bf0e3cbb5ac, + 0xbde94e8e43d0c8ec,0x3d52eeed1cbea317, + 0xed63a231d4c4fb27,0x4ca7aaa863ee4bdd, + 0x945e455f24fb1cf8,0x8fe8caa93e74ef6a, + 0xb975d6b6ee39e436,0xb3e2fd538e122b44, + 0xe7d34c64a9c85d44,0x60dbbca87196b616, + 0x90e40fbeea1d3a4a,0xbc8955e946fe31cd, + 0xb51d13aea4a488dd,0x6babab6398bdbe41, + 0xe264589a4dcdab14,0xc696963c7eed2dd1, + 0x8d7eb76070a08aec,0xfc1e1de5cf543ca2, + 0xb0de65388cc8ada8,0x3b25a55f43294bcb, + 0xdd15fe86affad912,0x49ef0eb713f39ebe, + 0x8a2dbf142dfcc7ab,0x6e3569326c784337, + 0xacb92ed9397bf996,0x49c2c37f07965404, + 0xd7e77a8f87daf7fb,0xdc33745ec97be906, + 0x86f0ac99b4e8dafd,0x69a028bb3ded71a3, + 0xa8acd7c0222311bc,0xc40832ea0d68ce0c, + 0xd2d80db02aabd62b,0xf50a3fa490c30190, + 0x83c7088e1aab65db,0x792667c6da79e0fa, + 0xa4b8cab1a1563f52,0x577001b891185938, + 0xcde6fd5e09abcf26,0xed4c0226b55e6f86, + 0x80b05e5ac60b6178,0x544f8158315b05b4, + 0xa0dc75f1778e39d6,0x696361ae3db1c721, + 0xc913936dd571c84c,0x3bc3a19cd1e38e9, + 0xfb5878494ace3a5f,0x4ab48a04065c723, + 0x9d174b2dcec0e47b,0x62eb0d64283f9c76, + 0xc45d1df942711d9a,0x3ba5d0bd324f8394, + 0xf5746577930d6500,0xca8f44ec7ee36479, + 0x9968bf6abbe85f20,0x7e998b13cf4e1ecb, + 0xbfc2ef456ae276e8,0x9e3fedd8c321a67e, + 0xefb3ab16c59b14a2,0xc5cfe94ef3ea101e, + 0x95d04aee3b80ece5,0xbba1f1d158724a12, + 0xbb445da9ca61281f,0x2a8a6e45ae8edc97, + 0xea1575143cf97226,0xf52d09d71a3293bd, + 0x924d692ca61be758,0x593c2626705f9c56, + 0xb6e0c377cfa2e12e,0x6f8b2fb00c77836c, + 0xe498f455c38b997a,0xb6dfb9c0f956447, + 0x8edf98b59a373fec,0x4724bd4189bd5eac, + 0xb2977ee300c50fe7,0x58edec91ec2cb657, + 0xdf3d5e9bc0f653e1,0x2f2967b66737e3ed, + 0x8b865b215899f46c,0xbd79e0d20082ee74, + 0xae67f1e9aec07187,0xecd8590680a3aa11, + 0xda01ee641a708de9,0xe80e6f4820cc9495, + 0x884134fe908658b2,0x3109058d147fdcdd, + 0xaa51823e34a7eede,0xbd4b46f0599fd415, + 0xd4e5e2cdc1d1ea96,0x6c9e18ac7007c91a, + 0x850fadc09923329e,0x3e2cf6bc604ddb0, + 0xa6539930bf6bff45,0x84db8346b786151c, + 0xcfe87f7cef46ff16,0xe612641865679a63, + 0x81f14fae158c5f6e,0x4fcb7e8f3f60c07e, + 0xa26da3999aef7749,0xe3be5e330f38f09d, + 0xcb090c8001ab551c,0x5cadf5bfd3072cc5, + 0xfdcb4fa002162a63,0x73d9732fc7c8f7f6, + 0x9e9f11c4014dda7e,0x2867e7fddcdd9afa, + 0xc646d63501a1511d,0xb281e1fd541501b8, + 0xf7d88bc24209a565,0x1f225a7ca91a4226, + 0x9ae757596946075f,0x3375788de9b06958, + 0xc1a12d2fc3978937,0x52d6b1641c83ae, + 0xf209787bb47d6b84,0xc0678c5dbd23a49a, + 0x9745eb4d50ce6332,0xf840b7ba963646e0, + 0xbd176620a501fbff,0xb650e5a93bc3d898, + 0xec5d3fa8ce427aff,0xa3e51f138ab4cebe, + 0x93ba47c980e98cdf,0xc66f336c36b10137, + 0xb8a8d9bbe123f017,0xb80b0047445d4184, + 0xe6d3102ad96cec1d,0xa60dc059157491e5, + 0x9043ea1ac7e41392,0x87c89837ad68db2f, + 0xb454e4a179dd1877,0x29babe4598c311fb, + 0xe16a1dc9d8545e94,0xf4296dd6fef3d67a, + 0x8ce2529e2734bb1d,0x1899e4a65f58660c, + 0xb01ae745b101e9e4,0x5ec05dcff72e7f8f, + 0xdc21a1171d42645d,0x76707543f4fa1f73, + 0x899504ae72497eba,0x6a06494a791c53a8, + 0xabfa45da0edbde69,0x487db9d17636892, + 0xd6f8d7509292d603,0x45a9d2845d3c42b6, + 0x865b86925b9bc5c2,0xb8a2392ba45a9b2, + 0xa7f26836f282b732,0x8e6cac7768d7141e, + 0xd1ef0244af2364ff,0x3207d795430cd926, + 0x8335616aed761f1f,0x7f44e6bd49e807b8, + 0xa402b9c5a8d3a6e7,0x5f16206c9c6209a6, + 0xcd036837130890a1,0x36dba887c37a8c0f, + 0x802221226be55a64,0xc2494954da2c9789, + 0xa02aa96b06deb0fd,0xf2db9baa10b7bd6c, + 0xc83553c5c8965d3d,0x6f92829494e5acc7, + 0xfa42a8b73abbf48c,0xcb772339ba1f17f9, + 0x9c69a97284b578d7,0xff2a760414536efb, + 0xc38413cf25e2d70d,0xfef5138519684aba, + 0xf46518c2ef5b8cd1,0x7eb258665fc25d69, + 0x98bf2f79d5993802,0xef2f773ffbd97a61, + 0xbeeefb584aff8603,0xaafb550ffacfd8fa, + 0xeeaaba2e5dbf6784,0x95ba2a53f983cf38, + 0x952ab45cfa97a0b2,0xdd945a747bf26183, + 0xba756174393d88df,0x94f971119aeef9e4, + 0xe912b9d1478ceb17,0x7a37cd5601aab85d, + 0x91abb422ccb812ee,0xac62e055c10ab33a, + 0xb616a12b7fe617aa,0x577b986b314d6009, + 0xe39c49765fdf9d94,0xed5a7e85fda0b80b, + 0x8e41ade9fbebc27d,0x14588f13be847307, + 0xb1d219647ae6b31c,0x596eb2d8ae258fc8, + 0xde469fbd99a05fe3,0x6fca5f8ed9aef3bb, + 0x8aec23d680043bee,0x25de7bb9480d5854, + 0xada72ccc20054ae9,0xaf561aa79a10ae6a, + 0xd910f7ff28069da4,0x1b2ba1518094da04, + 0x87aa9aff79042286,0x90fb44d2f05d0842, + 0xa99541bf57452b28,0x353a1607ac744a53, + 0xd3fa922f2d1675f2,0x42889b8997915ce8, + 0x847c9b5d7c2e09b7,0x69956135febada11, + 0xa59bc234db398c25,0x43fab9837e699095, + 0xcf02b2c21207ef2e,0x94f967e45e03f4bb, + 0x8161afb94b44f57d,0x1d1be0eebac278f5, + 0xa1ba1ba79e1632dc,0x6462d92a69731732, + 0xca28a291859bbf93,0x7d7b8f7503cfdcfe, + 0xfcb2cb35e702af78,0x5cda735244c3d43e, + 0x9defbf01b061adab,0x3a0888136afa64a7, + 0xc56baec21c7a1916,0x88aaa1845b8fdd0, + 0xf6c69a72a3989f5b,0x8aad549e57273d45, + 0x9a3c2087a63f6399,0x36ac54e2f678864b, + 0xc0cb28a98fcf3c7f,0x84576a1bb416a7dd, + 0xf0fdf2d3f3c30b9f,0x656d44a2a11c51d5, + 0x969eb7c47859e743,0x9f644ae5a4b1b325, + 0xbc4665b596706114,0x873d5d9f0dde1fee, + 0xeb57ff22fc0c7959,0xa90cb506d155a7ea, + 0x9316ff75dd87cbd8,0x9a7f12442d588f2, + 0xb7dcbf5354e9bece,0xc11ed6d538aeb2f, + 0xe5d3ef282a242e81,0x8f1668c8a86da5fa, + 0x8fa475791a569d10,0xf96e017d694487bc, + 0xb38d92d760ec4455,0x37c981dcc395a9ac, + 0xe070f78d3927556a,0x85bbe253f47b1417, + 0x8c469ab843b89562,0x93956d7478ccec8e, + 0xaf58416654a6babb,0x387ac8d1970027b2, + 0xdb2e51bfe9d0696a,0x6997b05fcc0319e, + 0x88fcf317f22241e2,0x441fece3bdf81f03, + 0xab3c2fddeeaad25a,0xd527e81cad7626c3, + 0xd60b3bd56a5586f1,0x8a71e223d8d3b074, + 0x85c7056562757456,0xf6872d5667844e49, + 0xa738c6bebb12d16c,0xb428f8ac016561db, + 0xd106f86e69d785c7,0xe13336d701beba52, + 0x82a45b450226b39c,0xecc0024661173473, + 0xa34d721642b06084,0x27f002d7f95d0190, + 0xcc20ce9bd35c78a5,0x31ec038df7b441f4, + 0xff290242c83396ce,0x7e67047175a15271, + 0x9f79a169bd203e41,0xf0062c6e984d386, + 0xc75809c42c684dd1,0x52c07b78a3e60868, + 0xf92e0c3537826145,0xa7709a56ccdf8a82, + 0x9bbcc7a142b17ccb,0x88a66076400bb691, + 0xc2abf989935ddbfe,0x6acff893d00ea435, + 0xf356f7ebf83552fe,0x583f6b8c4124d43, + 0x98165af37b2153de,0xc3727a337a8b704a, + 0xbe1bf1b059e9a8d6,0x744f18c0592e4c5c, + 0xeda2ee1c7064130c,0x1162def06f79df73, + 0x9485d4d1c63e8be7,0x8addcb5645ac2ba8, + 0xb9a74a0637ce2ee1,0x6d953e2bd7173692, + 0xe8111c87c5c1ba99,0xc8fa8db6ccdd0437, + 0x910ab1d4db9914a0,0x1d9c9892400a22a2, + 0xb54d5e4a127f59c8,0x2503beb6d00cab4b, + 0xe2a0b5dc971f303a,0x2e44ae64840fd61d, + 0x8da471a9de737e24,0x5ceaecfed289e5d2, + 0xb10d8e1456105dad,0x7425a83e872c5f47, + 0xdd50f1996b947518,0xd12f124e28f77719, + 0x8a5296ffe33cc92f,0x82bd6b70d99aaa6f, + 0xace73cbfdc0bfb7b,0x636cc64d1001550b, + 0xd8210befd30efa5a,0x3c47f7e05401aa4e, + 0x8714a775e3e95c78,0x65acfaec34810a71, + 0xa8d9d1535ce3b396,0x7f1839a741a14d0d, + 0xd31045a8341ca07c,0x1ede48111209a050, + 0x83ea2b892091e44d,0x934aed0aab460432, + 0xa4e4b66b68b65d60,0xf81da84d5617853f, + 0xce1de40642e3f4b9,0x36251260ab9d668e, + 0x80d2ae83e9ce78f3,0xc1d72b7c6b426019, + 0xa1075a24e4421730,0xb24cf65b8612f81f, + 0xc94930ae1d529cfc,0xdee033f26797b627, + 0xfb9b7cd9a4a7443c,0x169840ef017da3b1, + 0x9d412e0806e88aa5,0x8e1f289560ee864e, + 0xc491798a08a2ad4e,0xf1a6f2bab92a27e2, + 0xf5b5d7ec8acb58a2,0xae10af696774b1db, + 0x9991a6f3d6bf1765,0xacca6da1e0a8ef29, + 0xbff610b0cc6edd3f,0x17fd090a58d32af3, + 0xeff394dcff8a948e,0xddfc4b4cef07f5b0, + 0x95f83d0a1fb69cd9,0x4abdaf101564f98e, + 0xbb764c4ca7a4440f,0x9d6d1ad41abe37f1, + 0xea53df5fd18d5513,0x84c86189216dc5ed, + 0x92746b9be2f8552c,0x32fd3cf5b4e49bb4, + 0xb7118682dbb66a77,0x3fbc8c33221dc2a1, + 0xe4d5e82392a40515,0xfabaf3feaa5334a, + 0x8f05b1163ba6832d,0x29cb4d87f2a7400e, + 0xb2c71d5bca9023f8,0x743e20e9ef511012, + 0xdf78e4b2bd342cf6,0x914da9246b255416, + 0x8bab8eefb6409c1a,0x1ad089b6c2f7548e, + 0xae9672aba3d0c320,0xa184ac2473b529b1, + 0xda3c0f568cc4f3e8,0xc9e5d72d90a2741e, + 0x8865899617fb1871,0x7e2fa67c7a658892, + 0xaa7eebfb9df9de8d,0xddbb901b98feeab7, + 0xd51ea6fa85785631,0x552a74227f3ea565, + 0x8533285c936b35de,0xd53a88958f87275f, + 0xa67ff273b8460356,0x8a892abaf368f137, + 0xd01fef10a657842c,0x2d2b7569b0432d85, + 0x8213f56a67f6b29b,0x9c3b29620e29fc73, + 0xa298f2c501f45f42,0x8349f3ba91b47b8f, + 0xcb3f2f7642717713,0x241c70a936219a73, + 0xfe0efb53d30dd4d7,0xed238cd383aa0110, + 0x9ec95d1463e8a506,0xf4363804324a40aa, + 0xc67bb4597ce2ce48,0xb143c6053edcd0d5, + 0xf81aa16fdc1b81da,0xdd94b7868e94050a, + 0x9b10a4e5e9913128,0xca7cf2b4191c8326, + 0xc1d4ce1f63f57d72,0xfd1c2f611f63a3f0, + 0xf24a01a73cf2dccf,0xbc633b39673c8cec, + 0x976e41088617ca01,0xd5be0503e085d813, + 0xbd49d14aa79dbc82,0x4b2d8644d8a74e18, + 0xec9c459d51852ba2,0xddf8e7d60ed1219e, + 0x93e1ab8252f33b45,0xcabb90e5c942b503, + 0xb8da1662e7b00a17,0x3d6a751f3b936243, + 0xe7109bfba19c0c9d,0xcc512670a783ad4, + 0x906a617d450187e2,0x27fb2b80668b24c5, + 0xb484f9dc9641e9da,0xb1f9f660802dedf6, + 0xe1a63853bbd26451,0x5e7873f8a0396973, + 0x8d07e33455637eb2,0xdb0b487b6423e1e8, + 0xb049dc016abc5e5f,0x91ce1a9a3d2cda62, + 0xdc5c5301c56b75f7,0x7641a140cc7810fb, + 0x89b9b3e11b6329ba,0xa9e904c87fcb0a9d, + 0xac2820d9623bf429,0x546345fa9fbdcd44, + 0xd732290fbacaf133,0xa97c177947ad4095, + 0x867f59a9d4bed6c0,0x49ed8eabcccc485d, + 0xa81f301449ee8c70,0x5c68f256bfff5a74, + 0xd226fc195c6a2f8c,0x73832eec6fff3111, + 0x83585d8fd9c25db7,0xc831fd53c5ff7eab, + 0xa42e74f3d032f525,0xba3e7ca8b77f5e55, + 0xcd3a1230c43fb26f,0x28ce1bd2e55f35eb, + 0x80444b5e7aa7cf85,0x7980d163cf5b81b3, + 0xa0555e361951c366,0xd7e105bcc332621f, + 0xc86ab5c39fa63440,0x8dd9472bf3fefaa7, + 0xfa856334878fc150,0xb14f98f6f0feb951, + 0x9c935e00d4b9d8d2,0x6ed1bf9a569f33d3, + 0xc3b8358109e84f07,0xa862f80ec4700c8, + 0xf4a642e14c6262c8,0xcd27bb612758c0fa, + 0x98e7e9cccfbd7dbd,0x8038d51cb897789c, + 0xbf21e44003acdd2c,0xe0470a63e6bd56c3, + 0xeeea5d5004981478,0x1858ccfce06cac74, + 0x95527a5202df0ccb,0xf37801e0c43ebc8, + 0xbaa718e68396cffd,0xd30560258f54e6ba, + 0xe950df20247c83fd,0x47c6b82ef32a2069, + 0x91d28b7416cdd27e,0x4cdc331d57fa5441, + 0xb6472e511c81471d,0xe0133fe4adf8e952, + 0xe3d8f9e563a198e5,0x58180fddd97723a6, + 0x8e679c2f5e44ff8f,0x570f09eaa7ea7648,}; +}; + +template +constexpr uint64_t powers_template::power_of_five_128[number_of_entries]; + +using powers = powers_template<>; + +} // namespace fast_float + +#endif diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/float_common.h b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/float_common.h new file mode 100644 index 000000000..4a290f489 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/float_common.h @@ -0,0 +1,670 @@ +#ifndef FASTFLOAT_FLOAT_COMMON_H +#define FASTFLOAT_FLOAT_COMMON_H + +#include +#include +#include +#include +#include +#include + +#include "constexpr_feature_detect.h" + +namespace fast_float { + +enum chars_format { + scientific = 1 << 0, + fixed = 1 << 2, + hex = 1 << 3, + general = fixed | scientific +}; + +template +struct from_chars_result_t { + UC const* ptr; + std::errc ec; +}; +using from_chars_result = from_chars_result_t; + +template +struct parse_options_t { + constexpr explicit parse_options_t(chars_format fmt = chars_format::general, + UC dot = UC('.')) + : format(fmt), decimal_point(dot) {} + + /** Which number formats are accepted */ + chars_format format; + /** The character used as decimal point */ + UC decimal_point; +}; +using parse_options = parse_options_t; + +} + +#if FASTFLOAT_HAS_BIT_CAST +#include +#endif + +#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) \ + || defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) \ + || defined(__MINGW64__) \ + || defined(__s390x__) \ + || (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || defined(__PPC64LE__)) \ + || defined(__loongarch64) ) +#define FASTFLOAT_64BIT 1 +#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) \ + || defined(__arm__) || defined(_M_ARM) || defined(__ppc__) \ + || defined(__MINGW32__) || defined(__EMSCRIPTEN__)) +#define FASTFLOAT_32BIT 1 +#else + // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow. + // We can never tell the register width, but the SIZE_MAX is a good approximation. + // UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max portability. + #if SIZE_MAX == 0xffff + #error Unknown platform (16-bit, unsupported) + #elif SIZE_MAX == 0xffffffff + #define FASTFLOAT_32BIT 1 + #elif SIZE_MAX == 0xffffffffffffffff + #define FASTFLOAT_64BIT 1 + #else + #error Unknown platform (not 32-bit, not 64-bit?) + #endif +#endif + +#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) +#include +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +#define FASTFLOAT_VISUAL_STUDIO 1 +#endif + +#if defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__ +#define FASTFLOAT_IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#elif defined _WIN32 +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#if defined(__APPLE__) || defined(__FreeBSD__) +#include +#elif defined(sun) || defined(__sun) +#include +#elif defined(__MVS__) +#include +#else +#ifdef __has_include +#if __has_include() +#include +#endif //__has_include() +#endif //__has_include +#endif +# +#ifndef __BYTE_ORDER__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#ifndef __ORDER_LITTLE_ENDIAN__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#define FASTFLOAT_IS_BIG_ENDIAN 1 +#endif +#endif + +#if defined(__SSE2__) || \ + (defined(FASTFLOAT_VISUAL_STUDIO) && \ + (defined(_M_AMD64) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP == 2))) +#define FASTFLOAT_SSE2 1 +#endif + +#if defined(__aarch64__) || defined(_M_ARM64) +#define FASTFLOAT_NEON 1 +#endif + +#if defined(FASTFLOAT_SSE2) || defined(FASTFLOAT_ARM64) +#define FASTFLOAT_HAS_SIMD 1 +#endif + +#if defined(__GNUC__) +// disable -Wcast-align=strict (GCC only) +#define FASTFLOAT_SIMD_DISABLE_WARNINGS \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wcast-align\"") +#else +#define FASTFLOAT_SIMD_DISABLE_WARNINGS +#endif + +#if defined(__GNUC__) +#define FASTFLOAT_SIMD_RESTORE_WARNINGS \ + _Pragma("GCC diagnostic pop") +#else +#define FASTFLOAT_SIMD_RESTORE_WARNINGS +#endif + + + +#ifdef FASTFLOAT_VISUAL_STUDIO +#define fastfloat_really_inline __forceinline +#else +#define fastfloat_really_inline inline __attribute__((always_inline)) +#endif + +#ifndef FASTFLOAT_ASSERT +#define FASTFLOAT_ASSERT(x) { ((void)(x)); } +#endif + +#ifndef FASTFLOAT_DEBUG_ASSERT +#define FASTFLOAT_DEBUG_ASSERT(x) { ((void)(x)); } +#endif + +// rust style `try!()` macro, or `?` operator +#define FASTFLOAT_TRY(x) { if (!(x)) return false; } + +#define FASTFLOAT_ENABLE_IF(...) typename std::enable_if<(__VA_ARGS__), int>::type = 0 + + +namespace fast_float { + +fastfloat_really_inline constexpr bool cpp20_and_in_constexpr() { +#if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED + return std::is_constant_evaluated(); +#else + return false; +#endif +} + +// Compares two ASCII strings in a case insensitive manner. +template +inline FASTFLOAT_CONSTEXPR14 bool +fastfloat_strncasecmp(UC const * input1, UC const * input2, size_t length) { + char running_diff{0}; + for (size_t i = 0; i < length; ++i) { + running_diff |= (char(input1[i]) ^ char(input2[i])); + } + return (running_diff == 0) || (running_diff == 32); +} + +#ifndef FLT_EVAL_METHOD +#error "FLT_EVAL_METHOD should be defined, please include cfloat." +#endif + +// a pointer and a length to a contiguous block of memory +template +struct span { + const T* ptr; + size_t length; + constexpr span(const T* _ptr, size_t _length) : ptr(_ptr), length(_length) {} + constexpr span() : ptr(nullptr), length(0) {} + + constexpr size_t len() const noexcept { + return length; + } + + FASTFLOAT_CONSTEXPR14 const T& operator[](size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return ptr[index]; + } +}; + +struct value128 { + uint64_t low; + uint64_t high; + constexpr value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} + constexpr value128() : low(0), high(0) {} +}; + +/* Helper C++14 constexpr generic implementation of leading_zeroes */ +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 +int leading_zeroes_generic(uint64_t input_num, int last_bit = 0) { + if(input_num & uint64_t(0xffffffff00000000)) { input_num >>= 32; last_bit |= 32; } + if(input_num & uint64_t( 0xffff0000)) { input_num >>= 16; last_bit |= 16; } + if(input_num & uint64_t( 0xff00)) { input_num >>= 8; last_bit |= 8; } + if(input_num & uint64_t( 0xf0)) { input_num >>= 4; last_bit |= 4; } + if(input_num & uint64_t( 0xc)) { input_num >>= 2; last_bit |= 2; } + if(input_num & uint64_t( 0x2)) { input_num >>= 1; last_bit |= 1; } + return 63 - last_bit; +} + +/* result might be undefined when input_num is zero */ +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +int leading_zeroes(uint64_t input_num) { + assert(input_num > 0); + if (cpp20_and_in_constexpr()) { + return leading_zeroes_generic(input_num); + } +#ifdef FASTFLOAT_VISUAL_STUDIO + #if defined(_M_X64) || defined(_M_ARM64) + unsigned long leading_zero = 0; + // Search the mask data from most significant bit (MSB) + // to least significant bit (LSB) for a set bit (1). + _BitScanReverse64(&leading_zero, input_num); + return (int)(63 - leading_zero); + #else + return leading_zeroes_generic(input_num); + #endif +#else + return __builtin_clzll(input_num); +#endif +} + +// slow emulation routine for 32-bit +fastfloat_really_inline constexpr uint64_t emulu(uint32_t x, uint32_t y) { + return x * (uint64_t)y; +} + +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 +uint64_t umul128_generic(uint64_t ab, uint64_t cd, uint64_t *hi) { + uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); + uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); + uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); + uint64_t adbc_carry = !!(adbc < ad); + uint64_t lo = bd + (adbc << 32); + *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + + (adbc_carry << 32) + !!(lo < bd); + return lo; +} + +#ifdef FASTFLOAT_32BIT + +// slow emulation routine for 32-bit +#if !defined(__MINGW64__) +fastfloat_really_inline FASTFLOAT_CONSTEXPR14 +uint64_t _umul128(uint64_t ab, uint64_t cd, uint64_t *hi) { + return umul128_generic(ab, cd, hi); +} +#endif // !__MINGW64__ + +#endif // FASTFLOAT_32BIT + + +// compute 64-bit a*b +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +value128 full_multiplication(uint64_t a, uint64_t b) { + if (cpp20_and_in_constexpr()) { + value128 answer; + answer.low = umul128_generic(a, b, &answer.high); + return answer; + } + value128 answer; +#if defined(_M_ARM64) && !defined(__MINGW32__) + // ARM64 has native support for 64-bit multiplications, no need to emulate + // But MinGW on ARM64 doesn't have native support for 64-bit multiplications + answer.high = __umulh(a, b); + answer.low = a * b; +#elif defined(FASTFLOAT_32BIT) || (defined(_WIN64) && !defined(__clang__)) + answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 +#elif defined(FASTFLOAT_64BIT) + __uint128_t r = ((__uint128_t)a) * b; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); +#else + answer.low = umul128_generic(a, b, &answer.high); +#endif + return answer; +} + +struct adjusted_mantissa { + uint64_t mantissa{0}; + int32_t power2{0}; // a negative value indicates an invalid result + adjusted_mantissa() = default; + constexpr bool operator==(const adjusted_mantissa &o) const { + return mantissa == o.mantissa && power2 == o.power2; + } + constexpr bool operator!=(const adjusted_mantissa &o) const { + return mantissa != o.mantissa || power2 != o.power2; + } +}; + +// Bias so we can get the real exponent with an invalid adjusted_mantissa. +constexpr static int32_t invalid_am_bias = -0x8000; + +// used for binary_format_lookup_tables::max_mantissa +constexpr uint64_t constant_55555 = 5 * 5 * 5 * 5 * 5; + +template +struct binary_format_lookup_tables; + +template struct binary_format : binary_format_lookup_tables { + using equiv_uint = typename std::conditional::type; + + static inline constexpr int mantissa_explicit_bits(); + static inline constexpr int minimum_exponent(); + static inline constexpr int infinite_power(); + static inline constexpr int sign_index(); + static inline constexpr int min_exponent_fast_path(); // used when fegetround() == FE_TONEAREST + static inline constexpr int max_exponent_fast_path(); + static inline constexpr int max_exponent_round_to_even(); + static inline constexpr int min_exponent_round_to_even(); + static inline constexpr uint64_t max_mantissa_fast_path(int64_t power); + static inline constexpr uint64_t max_mantissa_fast_path(); // used when fegetround() == FE_TONEAREST + static inline constexpr int largest_power_of_ten(); + static inline constexpr int smallest_power_of_ten(); + static inline constexpr T exact_power_of_ten(int64_t power); + static inline constexpr size_t max_digits(); + static inline constexpr equiv_uint exponent_mask(); + static inline constexpr equiv_uint mantissa_mask(); + static inline constexpr equiv_uint hidden_bit_mask(); +}; + +template +struct binary_format_lookup_tables { + static constexpr double powers_of_ten[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; + + // Largest integer value v so that (5**index * v) <= 1<<53. + // 0x10000000000000 == 1 << 53 + static constexpr uint64_t max_mantissa[] = { + 0x10000000000000, + 0x10000000000000 / 5, + 0x10000000000000 / (5 * 5), + 0x10000000000000 / (5 * 5 * 5), + 0x10000000000000 / (5 * 5 * 5 * 5), + 0x10000000000000 / (constant_55555), + 0x10000000000000 / (constant_55555 * 5), + 0x10000000000000 / (constant_55555 * 5 * 5), + 0x10000000000000 / (constant_55555 * 5 * 5 * 5), + 0x10000000000000 / (constant_55555 * 5 * 5 * 5 * 5), + 0x10000000000000 / (constant_55555 * constant_55555), + 0x10000000000000 / (constant_55555 * constant_55555 * 5), + 0x10000000000000 / (constant_55555 * constant_55555 * 5 * 5), + 0x10000000000000 / (constant_55555 * constant_55555 * 5 * 5 * 5), + 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555), + 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5), + 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5 * 5), + 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5), + 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5 * 5), + 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555), + 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5), + 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5 * 5), + 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5), + 0x10000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5 * 5)}; +}; + +template +constexpr double binary_format_lookup_tables::powers_of_ten[]; + +template +constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; + +template +struct binary_format_lookup_tables { + static constexpr float powers_of_ten[] = {1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f, + 1e6f, 1e7f, 1e8f, 1e9f, 1e10f}; + + // Largest integer value v so that (5**index * v) <= 1<<24. + // 0x1000000 == 1<<24 + static constexpr uint64_t max_mantissa[] = { + 0x1000000, + 0x1000000 / 5, + 0x1000000 / (5 * 5), + 0x1000000 / (5 * 5 * 5), + 0x1000000 / (5 * 5 * 5 * 5), + 0x1000000 / (constant_55555), + 0x1000000 / (constant_55555 * 5), + 0x1000000 / (constant_55555 * 5 * 5), + 0x1000000 / (constant_55555 * 5 * 5 * 5), + 0x1000000 / (constant_55555 * 5 * 5 * 5 * 5), + 0x1000000 / (constant_55555 * constant_55555), + 0x1000000 / (constant_55555 * constant_55555 * 5)}; +}; + +template +constexpr float binary_format_lookup_tables::powers_of_ten[]; + +template +constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; + +template <> inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -22; +#endif +} + +template <> inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -10; +#endif +} + +template <> inline constexpr int binary_format::mantissa_explicit_bits() { + return 52; +} +template <> inline constexpr int binary_format::mantissa_explicit_bits() { + return 23; +} + +template <> inline constexpr int binary_format::max_exponent_round_to_even() { + return 23; +} + +template <> inline constexpr int binary_format::max_exponent_round_to_even() { + return 10; +} + +template <> inline constexpr int binary_format::min_exponent_round_to_even() { + return -4; +} + +template <> inline constexpr int binary_format::min_exponent_round_to_even() { + return -17; +} + +template <> inline constexpr int binary_format::minimum_exponent() { + return -1023; +} +template <> inline constexpr int binary_format::minimum_exponent() { + return -127; +} + +template <> inline constexpr int binary_format::infinite_power() { + return 0x7FF; +} +template <> inline constexpr int binary_format::infinite_power() { + return 0xFF; +} + +template <> inline constexpr int binary_format::sign_index() { return 63; } +template <> inline constexpr int binary_format::sign_index() { return 31; } + +template <> inline constexpr int binary_format::max_exponent_fast_path() { + return 22; +} +template <> inline constexpr int binary_format::max_exponent_fast_path() { + return 10; +} + +template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} +template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path(int64_t power) { + // caller is responsible to ensure that + // power >= 0 && power <= 22 + // + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)max_mantissa[0], max_mantissa[power]; +} +template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} +template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path(int64_t power) { + // caller is responsible to ensure that + // power >= 0 && power <= 10 + // + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)max_mantissa[0], max_mantissa[power]; +} + +template <> +inline constexpr double binary_format::exact_power_of_ten(int64_t power) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)powers_of_ten[0], powers_of_ten[power]; +} +template <> +inline constexpr float binary_format::exact_power_of_ten(int64_t power) { + // Work around clang bug https://godbolt.org/z/zedh7rrhc + return (void)powers_of_ten[0], powers_of_ten[power]; +} + + +template <> +inline constexpr int binary_format::largest_power_of_ten() { + return 308; +} +template <> +inline constexpr int binary_format::largest_power_of_ten() { + return 38; +} + +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -342; +} +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -65; +} + +template <> inline constexpr size_t binary_format::max_digits() { + return 769; +} +template <> inline constexpr size_t binary_format::max_digits() { + return 114; +} + +template <> inline constexpr binary_format::equiv_uint + binary_format::exponent_mask() { + return 0x7F800000; +} +template <> inline constexpr binary_format::equiv_uint + binary_format::exponent_mask() { + return 0x7FF0000000000000; +} + +template <> inline constexpr binary_format::equiv_uint + binary_format::mantissa_mask() { + return 0x007FFFFF; +} +template <> inline constexpr binary_format::equiv_uint + binary_format::mantissa_mask() { + return 0x000FFFFFFFFFFFFF; +} + +template <> inline constexpr binary_format::equiv_uint + binary_format::hidden_bit_mask() { + return 0x00800000; +} +template <> inline constexpr binary_format::equiv_uint + binary_format::hidden_bit_mask() { + return 0x0010000000000000; +} + +template +fastfloat_really_inline FASTFLOAT_CONSTEXPR20 +void to_float(bool negative, adjusted_mantissa am, T &value) { + using fastfloat_uint = typename binary_format::equiv_uint; + fastfloat_uint word = (fastfloat_uint)am.mantissa; + word |= fastfloat_uint(am.power2) << binary_format::mantissa_explicit_bits(); + word |= fastfloat_uint(negative) << binary_format::sign_index(); +#if FASTFLOAT_HAS_BIT_CAST + value = std::bit_cast(word); +#else + ::memcpy(&value, &word, sizeof(T)); +#endif +} + +#ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default +template +struct space_lut { + static constexpr bool value[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +}; + +template +constexpr bool space_lut::value[]; + +inline constexpr bool is_space(uint8_t c) { return space_lut<>::value[c]; } +#endif + +template +static constexpr uint64_t int_cmp_zeros() +{ + static_assert((sizeof(UC) == 1) || (sizeof(UC) == 2) || (sizeof(UC) == 4), "Unsupported character size"); + return (sizeof(UC) == 1) ? 0x3030303030303030 : (sizeof(UC) == 2) ? (uint64_t(UC('0')) << 48 | uint64_t(UC('0')) << 32 | uint64_t(UC('0')) << 16 | UC('0')) : (uint64_t(UC('0')) << 32 | UC('0')); +} +template +static constexpr int int_cmp_len() +{ + return sizeof(uint64_t) / sizeof(UC); +} +template +static constexpr UC const * str_const_nan() +{ + return nullptr; +} +template<> +constexpr char const * str_const_nan() +{ + return "nan"; +} +template<> +constexpr wchar_t const * str_const_nan() +{ + return L"nan"; +} +template<> +constexpr char16_t const * str_const_nan() +{ + return u"nan"; +} +template<> +constexpr char32_t const * str_const_nan() +{ + return U"nan"; +} +template +static constexpr UC const * str_const_inf() +{ + return nullptr; +} +template<> +constexpr char const * str_const_inf() +{ + return "infinity"; +} +template<> +constexpr wchar_t const * str_const_inf() +{ + return L"infinity"; +} +template<> +constexpr char16_t const * str_const_inf() +{ + return u"infinity"; +} +template<> +constexpr char32_t const * str_const_inf() +{ + return U"infinity"; +} +} // namespace fast_float + +#endif diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/parse_number.h b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/parse_number.h new file mode 100644 index 000000000..e077b9d03 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/parse_number.h @@ -0,0 +1,231 @@ +#ifndef FASTFLOAT_PARSE_NUMBER_H +#define FASTFLOAT_PARSE_NUMBER_H + +#include "ascii_number.h" +#include "decimal_to_binary.h" +#include "digit_comparison.h" +#include "float_common.h" + +#include +#include +#include +#include + +namespace fast_float { + + +namespace detail { +/** + * Special case +inf, -inf, nan, infinity, -infinity. + * The case comparisons could be made much faster given that we know that the + * strings a null-free and fixed. + **/ +template +from_chars_result_t FASTFLOAT_CONSTEXPR14 +parse_infnan(UC const * first, UC const * last, T &value) noexcept { + from_chars_result_t answer{}; + answer.ptr = first; + answer.ec = std::errc(); // be optimistic + bool minusSign = false; + if (*first == UC('-')) { // assume first < last, so dereference without checks; C++17 20.19.3.(7.1) explicitly forbids '+' here + minusSign = true; + ++first; + } +#ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default + if (*first == UC('+')) { + ++first; + } +#endif + if (last - first >= 3) { + if (fastfloat_strncasecmp(first, str_const_nan(), 3)) { + answer.ptr = (first += 3); + value = minusSign ? -std::numeric_limits::quiet_NaN() : std::numeric_limits::quiet_NaN(); + // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). + if(first != last && *first == UC('(')) { + for(UC const * ptr = first + 1; ptr != last; ++ptr) { + if (*ptr == UC(')')) { + answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) + break; + } + else if(!((UC('a') <= *ptr && *ptr <= UC('z')) || (UC('A') <= *ptr && *ptr <= UC('Z')) || (UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_'))) + break; // forbidden char, not nan(n-char-seq-opt) + } + } + return answer; + } + if (fastfloat_strncasecmp(first, str_const_inf(), 3)) { + if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, str_const_inf() + 3, 5)) { + answer.ptr = first + 8; + } else { + answer.ptr = first + 3; + } + value = minusSign ? -std::numeric_limits::infinity() : std::numeric_limits::infinity(); + return answer; + } + } + answer.ec = std::errc::invalid_argument; + return answer; +} + +/** + * Returns true if the floating-pointing rounding mode is to 'nearest'. + * It is the default on most system. This function is meant to be inexpensive. + * Credit : @mwalcott3 + */ +fastfloat_really_inline bool rounds_to_nearest() noexcept { + // https://lemire.me/blog/2020/06/26/gcc-not-nearest/ +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return false; +#endif + // See + // A fast function to check your floating-point rounding mode + // https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/ + // + // This function is meant to be equivalent to : + // prior: #include + // return fegetround() == FE_TONEAREST; + // However, it is expected to be much faster than the fegetround() + // function call. + // + // The volatile keywoard prevents the compiler from computing the function + // at compile-time. + // There might be other ways to prevent compile-time optimizations (e.g., asm). + // The value does not need to be std::numeric_limits::min(), any small + // value so that 1 + x should round to 1 would do (after accounting for excess + // precision, as in 387 instructions). + static volatile float fmin = std::numeric_limits::min(); + float fmini = fmin; // we copy it so that it gets loaded at most once. + // + // Explanation: + // Only when fegetround() == FE_TONEAREST do we have that + // fmin + 1.0f == 1.0f - fmin. + // + // FE_UPWARD: + // fmin + 1.0f > 1 + // 1.0f - fmin == 1 + // + // FE_DOWNWARD or FE_TOWARDZERO: + // fmin + 1.0f == 1 + // 1.0f - fmin < 1 + // + // Note: This may fail to be accurate if fast-math has been + // enabled, as rounding conventions may not apply. + #ifdef FASTFLOAT_VISUAL_STUDIO + # pragma warning(push) + // todo: is there a VS warning? + // see https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013 + #elif defined(__clang__) + # pragma clang diagnostic push + # pragma clang diagnostic ignored "-Wfloat-equal" + #elif defined(__GNUC__) + # pragma GCC diagnostic push + # pragma GCC diagnostic ignored "-Wfloat-equal" + #endif + return (fmini + 1.0f == 1.0f - fmini); + #ifdef FASTFLOAT_VISUAL_STUDIO + # pragma warning(pop) + #elif defined(__clang__) + # pragma clang diagnostic pop + #elif defined(__GNUC__) + # pragma GCC diagnostic pop + #endif +} + +} // namespace detail + +template +FASTFLOAT_CONSTEXPR20 +from_chars_result_t from_chars(UC const * first, UC const * last, + T &value, chars_format fmt /*= chars_format::general*/) noexcept { + return from_chars_advanced(first, last, value, parse_options_t{fmt}); +} + +template +FASTFLOAT_CONSTEXPR20 +from_chars_result_t from_chars_advanced(UC const * first, UC const * last, + T &value, parse_options_t options) noexcept { + + static_assert (std::is_same::value || std::is_same::value, "only float and double are supported"); + static_assert (std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value , "only char, wchar_t, char16_t and char32_t are supported"); + + from_chars_result_t answer; +#ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default + while ((first != last) && fast_float::is_space(uint8_t(*first))) { + first++; + } +#endif + if (first == last) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + parsed_number_string_t pns = parse_number_string(first, last, options); + if (!pns.valid) { + return detail::parse_infnan(first, last, value); + } + + answer.ec = std::errc(); // be optimistic + answer.ptr = pns.lastmatch; + // The implementation of the Clinger's fast path is convoluted because + // we want round-to-nearest in all cases, irrespective of the rounding mode + // selected on the thread. + // We proceed optimistically, assuming that detail::rounds_to_nearest() returns + // true. + if (binary_format::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format::max_exponent_fast_path() && !pns.too_many_digits) { + // Unfortunately, the conventional Clinger's fast path is only possible + // when the system rounds to the nearest float. + // + // We expect the next branch to almost always be selected. + // We could check it first (before the previous branch), but + // there might be performance advantages at having the check + // be last. + if(!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { + // We have that fegetround() == FE_TONEAREST. + // Next is Clinger's fast path. + if (pns.mantissa <=binary_format::max_mantissa_fast_path()) { + value = T(pns.mantissa); + if (pns.exponent < 0) { value = value / binary_format::exact_power_of_ten(-pns.exponent); } + else { value = value * binary_format::exact_power_of_ten(pns.exponent); } + if (pns.negative) { value = -value; } + return answer; + } + } else { + // We do not have that fegetround() == FE_TONEAREST. + // Next is a modified Clinger's fast path, inspired by Jakub Jelínek's proposal + if (pns.exponent >= 0 && pns.mantissa <=binary_format::max_mantissa_fast_path(pns.exponent)) { +#if defined(__clang__) + // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD + if(pns.mantissa == 0) { + value = pns.negative ? -0. : 0.; + return answer; + } +#endif + value = T(pns.mantissa) * binary_format::exact_power_of_ten(pns.exponent); + if (pns.negative) { value = -value; } + return answer; + } + } + } + adjusted_mantissa am = compute_float>(pns.exponent, pns.mantissa); + if(pns.too_many_digits && am.power2 >= 0) { + if(am != compute_float>(pns.exponent, pns.mantissa + 1)) { + am = compute_error>(pns.exponent, pns.mantissa); + } + } + // If we called compute_float>(pns.exponent, pns.mantissa) and we have an invalid power (am.power2 < 0), + // then we need to go the long way around again. This is very uncommon. + if(am.power2 < 0) { am = digit_comp(pns, am); } + to_float(pns.negative, am, value); + // Test for over/underflow. + if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || am.power2 == binary_format::infinite_power()) { + answer.ec = std::errc::result_out_of_range; + } + return answer; +} + +} // namespace fast_float + +#endif diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/simple_decimal_conversion.h b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/simple_decimal_conversion.h new file mode 100644 index 000000000..e87801480 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/fast_float/include/fast_float/simple_decimal_conversion.h @@ -0,0 +1,360 @@ +#ifndef FASTFLOAT_GENERIC_DECIMAL_TO_BINARY_H +#define FASTFLOAT_GENERIC_DECIMAL_TO_BINARY_H + +/** + * This code is meant to handle the case where we have more than 19 digits. + * + * It is based on work by Nigel Tao (at https://github.com/google/wuffs/) + * who credits Ken Thompson for the design (via a reference to the Go source + * code). + * + * Rob Pike suggested that this algorithm be called "Simple Decimal Conversion". + * + * It is probably not very fast but it is a fallback that should almost never + * be used in real life. Though it is not fast, it is "easily" understood and debugged. + **/ +#include "ascii_number.h" +#include "decimal_to_binary.h" +#include + +namespace fast_float { + +namespace detail { + +// remove all final zeroes +inline void trim(decimal &h) { + while ((h.num_digits > 0) && (h.digits[h.num_digits - 1] == 0)) { + h.num_digits--; + } +} + + + +inline uint32_t number_of_digits_decimal_left_shift(const decimal &h, uint32_t shift) { + shift &= 63; + constexpr uint16_t number_of_digits_decimal_left_shift_table[65] = { + 0x0000, 0x0800, 0x0801, 0x0803, 0x1006, 0x1009, 0x100D, 0x1812, 0x1817, + 0x181D, 0x2024, 0x202B, 0x2033, 0x203C, 0x2846, 0x2850, 0x285B, 0x3067, + 0x3073, 0x3080, 0x388E, 0x389C, 0x38AB, 0x38BB, 0x40CC, 0x40DD, 0x40EF, + 0x4902, 0x4915, 0x4929, 0x513E, 0x5153, 0x5169, 0x5180, 0x5998, 0x59B0, + 0x59C9, 0x61E3, 0x61FD, 0x6218, 0x6A34, 0x6A50, 0x6A6D, 0x6A8B, 0x72AA, + 0x72C9, 0x72E9, 0x7B0A, 0x7B2B, 0x7B4D, 0x8370, 0x8393, 0x83B7, 0x83DC, + 0x8C02, 0x8C28, 0x8C4F, 0x9477, 0x949F, 0x94C8, 0x9CF2, 0x051C, 0x051C, + 0x051C, 0x051C, + }; + uint32_t x_a = number_of_digits_decimal_left_shift_table[shift]; + uint32_t x_b = number_of_digits_decimal_left_shift_table[shift + 1]; + uint32_t num_new_digits = x_a >> 11; + uint32_t pow5_a = 0x7FF & x_a; + uint32_t pow5_b = 0x7FF & x_b; + constexpr uint8_t + number_of_digits_decimal_left_shift_table_powers_of_5[0x051C] = { + 5, 2, 5, 1, 2, 5, 6, 2, 5, 3, 1, 2, 5, 1, 5, 6, 2, 5, 7, 8, 1, 2, 5, 3, + 9, 0, 6, 2, 5, 1, 9, 5, 3, 1, 2, 5, 9, 7, 6, 5, 6, 2, 5, 4, 8, 8, 2, 8, + 1, 2, 5, 2, 4, 4, 1, 4, 0, 6, 2, 5, 1, 2, 2, 0, 7, 0, 3, 1, 2, 5, 6, 1, + 0, 3, 5, 1, 5, 6, 2, 5, 3, 0, 5, 1, 7, 5, 7, 8, 1, 2, 5, 1, 5, 2, 5, 8, + 7, 8, 9, 0, 6, 2, 5, 7, 6, 2, 9, 3, 9, 4, 5, 3, 1, 2, 5, 3, 8, 1, 4, 6, + 9, 7, 2, 6, 5, 6, 2, 5, 1, 9, 0, 7, 3, 4, 8, 6, 3, 2, 8, 1, 2, 5, 9, 5, + 3, 6, 7, 4, 3, 1, 6, 4, 0, 6, 2, 5, 4, 7, 6, 8, 3, 7, 1, 5, 8, 2, 0, 3, + 1, 2, 5, 2, 3, 8, 4, 1, 8, 5, 7, 9, 1, 0, 1, 5, 6, 2, 5, 1, 1, 9, 2, 0, + 9, 2, 8, 9, 5, 5, 0, 7, 8, 1, 2, 5, 5, 9, 6, 0, 4, 6, 4, 4, 7, 7, 5, 3, + 9, 0, 6, 2, 5, 2, 9, 8, 0, 2, 3, 2, 2, 3, 8, 7, 6, 9, 5, 3, 1, 2, 5, 1, + 4, 9, 0, 1, 1, 6, 1, 1, 9, 3, 8, 4, 7, 6, 5, 6, 2, 5, 7, 4, 5, 0, 5, 8, + 0, 5, 9, 6, 9, 2, 3, 8, 2, 8, 1, 2, 5, 3, 7, 2, 5, 2, 9, 0, 2, 9, 8, 4, + 6, 1, 9, 1, 4, 0, 6, 2, 5, 1, 8, 6, 2, 6, 4, 5, 1, 4, 9, 2, 3, 0, 9, 5, + 7, 0, 3, 1, 2, 5, 9, 3, 1, 3, 2, 2, 5, 7, 4, 6, 1, 5, 4, 7, 8, 5, 1, 5, + 6, 2, 5, 4, 6, 5, 6, 6, 1, 2, 8, 7, 3, 0, 7, 7, 3, 9, 2, 5, 7, 8, 1, 2, + 5, 2, 3, 2, 8, 3, 0, 6, 4, 3, 6, 5, 3, 8, 6, 9, 6, 2, 8, 9, 0, 6, 2, 5, + 1, 1, 6, 4, 1, 5, 3, 2, 1, 8, 2, 6, 9, 3, 4, 8, 1, 4, 4, 5, 3, 1, 2, 5, + 5, 8, 2, 0, 7, 6, 6, 0, 9, 1, 3, 4, 6, 7, 4, 0, 7, 2, 2, 6, 5, 6, 2, 5, + 2, 9, 1, 0, 3, 8, 3, 0, 4, 5, 6, 7, 3, 3, 7, 0, 3, 6, 1, 3, 2, 8, 1, 2, + 5, 1, 4, 5, 5, 1, 9, 1, 5, 2, 2, 8, 3, 6, 6, 8, 5, 1, 8, 0, 6, 6, 4, 0, + 6, 2, 5, 7, 2, 7, 5, 9, 5, 7, 6, 1, 4, 1, 8, 3, 4, 2, 5, 9, 0, 3, 3, 2, + 0, 3, 1, 2, 5, 3, 6, 3, 7, 9, 7, 8, 8, 0, 7, 0, 9, 1, 7, 1, 2, 9, 5, 1, + 6, 6, 0, 1, 5, 6, 2, 5, 1, 8, 1, 8, 9, 8, 9, 4, 0, 3, 5, 4, 5, 8, 5, 6, + 4, 7, 5, 8, 3, 0, 0, 7, 8, 1, 2, 5, 9, 0, 9, 4, 9, 4, 7, 0, 1, 7, 7, 2, + 9, 2, 8, 2, 3, 7, 9, 1, 5, 0, 3, 9, 0, 6, 2, 5, 4, 5, 4, 7, 4, 7, 3, 5, + 0, 8, 8, 6, 4, 6, 4, 1, 1, 8, 9, 5, 7, 5, 1, 9, 5, 3, 1, 2, 5, 2, 2, 7, + 3, 7, 3, 6, 7, 5, 4, 4, 3, 2, 3, 2, 0, 5, 9, 4, 7, 8, 7, 5, 9, 7, 6, 5, + 6, 2, 5, 1, 1, 3, 6, 8, 6, 8, 3, 7, 7, 2, 1, 6, 1, 6, 0, 2, 9, 7, 3, 9, + 3, 7, 9, 8, 8, 2, 8, 1, 2, 5, 5, 6, 8, 4, 3, 4, 1, 8, 8, 6, 0, 8, 0, 8, + 0, 1, 4, 8, 6, 9, 6, 8, 9, 9, 4, 1, 4, 0, 6, 2, 5, 2, 8, 4, 2, 1, 7, 0, + 9, 4, 3, 0, 4, 0, 4, 0, 0, 7, 4, 3, 4, 8, 4, 4, 9, 7, 0, 7, 0, 3, 1, 2, + 5, 1, 4, 2, 1, 0, 8, 5, 4, 7, 1, 5, 2, 0, 2, 0, 0, 3, 7, 1, 7, 4, 2, 2, + 4, 8, 5, 3, 5, 1, 5, 6, 2, 5, 7, 1, 0, 5, 4, 2, 7, 3, 5, 7, 6, 0, 1, 0, + 0, 1, 8, 5, 8, 7, 1, 1, 2, 4, 2, 6, 7, 5, 7, 8, 1, 2, 5, 3, 5, 5, 2, 7, + 1, 3, 6, 7, 8, 8, 0, 0, 5, 0, 0, 9, 2, 9, 3, 5, 5, 6, 2, 1, 3, 3, 7, 8, + 9, 0, 6, 2, 5, 1, 7, 7, 6, 3, 5, 6, 8, 3, 9, 4, 0, 0, 2, 5, 0, 4, 6, 4, + 6, 7, 7, 8, 1, 0, 6, 6, 8, 9, 4, 5, 3, 1, 2, 5, 8, 8, 8, 1, 7, 8, 4, 1, + 9, 7, 0, 0, 1, 2, 5, 2, 3, 2, 3, 3, 8, 9, 0, 5, 3, 3, 4, 4, 7, 2, 6, 5, + 6, 2, 5, 4, 4, 4, 0, 8, 9, 2, 0, 9, 8, 5, 0, 0, 6, 2, 6, 1, 6, 1, 6, 9, + 4, 5, 2, 6, 6, 7, 2, 3, 6, 3, 2, 8, 1, 2, 5, 2, 2, 2, 0, 4, 4, 6, 0, 4, + 9, 2, 5, 0, 3, 1, 3, 0, 8, 0, 8, 4, 7, 2, 6, 3, 3, 3, 6, 1, 8, 1, 6, 4, + 0, 6, 2, 5, 1, 1, 1, 0, 2, 2, 3, 0, 2, 4, 6, 2, 5, 1, 5, 6, 5, 4, 0, 4, + 2, 3, 6, 3, 1, 6, 6, 8, 0, 9, 0, 8, 2, 0, 3, 1, 2, 5, 5, 5, 5, 1, 1, 1, + 5, 1, 2, 3, 1, 2, 5, 7, 8, 2, 7, 0, 2, 1, 1, 8, 1, 5, 8, 3, 4, 0, 4, 5, + 4, 1, 0, 1, 5, 6, 2, 5, 2, 7, 7, 5, 5, 5, 7, 5, 6, 1, 5, 6, 2, 8, 9, 1, + 3, 5, 1, 0, 5, 9, 0, 7, 9, 1, 7, 0, 2, 2, 7, 0, 5, 0, 7, 8, 1, 2, 5, 1, + 3, 8, 7, 7, 7, 8, 7, 8, 0, 7, 8, 1, 4, 4, 5, 6, 7, 5, 5, 2, 9, 5, 3, 9, + 5, 8, 5, 1, 1, 3, 5, 2, 5, 3, 9, 0, 6, 2, 5, 6, 9, 3, 8, 8, 9, 3, 9, 0, + 3, 9, 0, 7, 2, 2, 8, 3, 7, 7, 6, 4, 7, 6, 9, 7, 9, 2, 5, 5, 6, 7, 6, 2, + 6, 9, 5, 3, 1, 2, 5, 3, 4, 6, 9, 4, 4, 6, 9, 5, 1, 9, 5, 3, 6, 1, 4, 1, + 8, 8, 8, 2, 3, 8, 4, 8, 9, 6, 2, 7, 8, 3, 8, 1, 3, 4, 7, 6, 5, 6, 2, 5, + 1, 7, 3, 4, 7, 2, 3, 4, 7, 5, 9, 7, 6, 8, 0, 7, 0, 9, 4, 4, 1, 1, 9, 2, + 4, 4, 8, 1, 3, 9, 1, 9, 0, 6, 7, 3, 8, 2, 8, 1, 2, 5, 8, 6, 7, 3, 6, 1, + 7, 3, 7, 9, 8, 8, 4, 0, 3, 5, 4, 7, 2, 0, 5, 9, 6, 2, 2, 4, 0, 6, 9, 5, + 9, 5, 3, 3, 6, 9, 1, 4, 0, 6, 2, 5, + }; + const uint8_t *pow5 = + &number_of_digits_decimal_left_shift_table_powers_of_5[pow5_a]; + uint32_t i = 0; + uint32_t n = pow5_b - pow5_a; + for (; i < n; i++) { + if (i >= h.num_digits) { + return num_new_digits - 1; + } else if (h.digits[i] == pow5[i]) { + continue; + } else if (h.digits[i] < pow5[i]) { + return num_new_digits - 1; + } else { + return num_new_digits; + } + } + return num_new_digits; +} + +inline uint64_t round(decimal &h) { + if ((h.num_digits == 0) || (h.decimal_point < 0)) { + return 0; + } else if (h.decimal_point > 18) { + return UINT64_MAX; + } + // at this point, we know that h.decimal_point >= 0 + uint32_t dp = uint32_t(h.decimal_point); + uint64_t n = 0; + for (uint32_t i = 0; i < dp; i++) { + n = (10 * n) + ((i < h.num_digits) ? h.digits[i] : 0); + } + bool round_up = false; + if (dp < h.num_digits) { + round_up = h.digits[dp] >= 5; // normally, we round up + // but we may need to round to even! + if ((h.digits[dp] == 5) && (dp + 1 == h.num_digits)) { + round_up = h.truncated || ((dp > 0) && (1 & h.digits[dp - 1])); + } + } + if (round_up) { + n++; + } + return n; +} + +// computes h * 2^-shift +inline void decimal_left_shift(decimal &h, uint32_t shift) { + if (h.num_digits == 0) { + return; + } + uint32_t num_new_digits = number_of_digits_decimal_left_shift(h, shift); + int32_t read_index = int32_t(h.num_digits - 1); + uint32_t write_index = h.num_digits - 1 + num_new_digits; + uint64_t n = 0; + + while (read_index >= 0) { + n += uint64_t(h.digits[read_index]) << shift; + uint64_t quotient = n / 10; + uint64_t remainder = n - (10 * quotient); + if (write_index < max_digits) { + h.digits[write_index] = uint8_t(remainder); + } else if (remainder > 0) { + h.truncated = true; + } + n = quotient; + write_index--; + read_index--; + } + while (n > 0) { + uint64_t quotient = n / 10; + uint64_t remainder = n - (10 * quotient); + if (write_index < max_digits) { + h.digits[write_index] = uint8_t(remainder); + } else if (remainder > 0) { + h.truncated = true; + } + n = quotient; + write_index--; + } + h.num_digits += num_new_digits; + if (h.num_digits > max_digits) { + h.num_digits = max_digits; + } + h.decimal_point += int32_t(num_new_digits); + trim(h); +} + +// computes h * 2^shift +inline void decimal_right_shift(decimal &h, uint32_t shift) { + uint32_t read_index = 0; + uint32_t write_index = 0; + + uint64_t n = 0; + + while ((n >> shift) == 0) { + if (read_index < h.num_digits) { + n = (10 * n) + h.digits[read_index++]; + } else if (n == 0) { + return; + } else { + while ((n >> shift) == 0) { + n = 10 * n; + read_index++; + } + break; + } + } + h.decimal_point -= int32_t(read_index - 1); + if (h.decimal_point < -decimal_point_range) { // it is zero + h.num_digits = 0; + h.decimal_point = 0; + h.negative = false; + h.truncated = false; + return; + } + uint64_t mask = (uint64_t(1) << shift) - 1; + while (read_index < h.num_digits) { + uint8_t new_digit = uint8_t(n >> shift); + n = (10 * (n & mask)) + h.digits[read_index++]; + h.digits[write_index++] = new_digit; + } + while (n > 0) { + uint8_t new_digit = uint8_t(n >> shift); + n = 10 * (n & mask); + if (write_index < max_digits) { + h.digits[write_index++] = new_digit; + } else if (new_digit > 0) { + h.truncated = true; + } + } + h.num_digits = write_index; + trim(h); +} + +} // namespace detail + +template +adjusted_mantissa compute_float(decimal &d) { + adjusted_mantissa answer; + if (d.num_digits == 0) { + // should be zero + answer.power2 = 0; + answer.mantissa = 0; + return answer; + } + // At this point, going further, we can assume that d.num_digits > 0. + // + // We want to guard against excessive decimal point values because + // they can result in long running times. Indeed, we do + // shifts by at most 60 bits. We have that log(10**400)/log(2**60) ~= 22 + // which is fine, but log(10**299995)/log(2**60) ~= 16609 which is not + // fine (runs for a long time). + // + if(d.decimal_point < -324) { + // We have something smaller than 1e-324 which is always zero + // in binary64 and binary32. + // It should be zero. + answer.power2 = 0; + answer.mantissa = 0; + return answer; + } else if(d.decimal_point >= 310) { + // We have something at least as large as 0.1e310 which is + // always infinite. + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + constexpr uint32_t max_shift = 60; + constexpr uint32_t num_powers = 19; + constexpr uint8_t decimal_powers[19] = { + 0, 3, 6, 9, 13, 16, 19, 23, 26, 29, // + 33, 36, 39, 43, 46, 49, 53, 56, 59, // + }; + int32_t exp2 = 0; + while (d.decimal_point > 0) { + uint32_t n = uint32_t(d.decimal_point); + uint32_t shift = (n < num_powers) ? decimal_powers[n] : max_shift; + detail::decimal_right_shift(d, shift); + if (d.decimal_point < -decimal_point_range) { + // should be zero + answer.power2 = 0; + answer.mantissa = 0; + return answer; + } + exp2 += int32_t(shift); + } + // We shift left toward [1/2 ... 1]. + while (d.decimal_point <= 0) { + uint32_t shift; + if (d.decimal_point == 0) { + if (d.digits[0] >= 5) { + break; + } + shift = (d.digits[0] < 2) ? 2 : 1; + } else { + uint32_t n = uint32_t(-d.decimal_point); + shift = (n < num_powers) ? decimal_powers[n] : max_shift; + } + detail::decimal_left_shift(d, shift); + if (d.decimal_point > decimal_point_range) { + // we want to get infinity: + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + exp2 -= int32_t(shift); + } + // We are now in the range [1/2 ... 1] but the binary format uses [1 ... 2]. + exp2--; + constexpr int32_t minimum_exponent = binary::minimum_exponent(); + while ((minimum_exponent + 1) > exp2) { + uint32_t n = uint32_t((minimum_exponent + 1) - exp2); + if (n > max_shift) { + n = max_shift; + } + detail::decimal_right_shift(d, n); + exp2 += int32_t(n); + } + if ((exp2 - minimum_exponent) >= binary::infinite_power()) { + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + + const int mantissa_size_in_bits = binary::mantissa_explicit_bits() + 1; + detail::decimal_left_shift(d, mantissa_size_in_bits); + + uint64_t mantissa = detail::round(d); + // It is possible that we have an overflow, in which case we need + // to shift back. + if(mantissa >= (uint64_t(1) << mantissa_size_in_bits)) { + detail::decimal_right_shift(d, 1); + exp2 += 1; + mantissa = detail::round(d); + if ((exp2 - minimum_exponent) >= binary::infinite_power()) { + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + } + answer.power2 = exp2 - binary::minimum_exponent(); + if(mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) { answer.power2--; } + answer.mantissa = mantissa & ((uint64_t(1) << binary::mantissa_explicit_bits()) - 1); + return answer; +} + +template +adjusted_mantissa parse_long_mantissa(const char *first, const char* last, parse_options options) { + decimal d = parse_decimal(first, last, options); + return compute_float(d); +} + +} // namespace fast_float +#endif diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/LICENSE b/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/LICENSE new file mode 100644 index 000000000..8b24faa71 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018, Steffen Schümann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/README.md b/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/README.md new file mode 100644 index 000000000..db1b2f9ba --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/README.md @@ -0,0 +1,1074 @@ +![Supported Platforms](https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows%20%7C%20FreeBSD-blue.svg) +![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg) +[![CMake Build Matrix](https://github.com/gulrak/filesystem/actions/workflows/build_cmake.yml/badge.svg?branch=master)](https://github.com/gulrak/filesystem/actions/workflows/build_cmake.yml) +[![Build Status](https://ci.appveyor.com/api/projects/status/t07wp3k2cddo0hpo/branch/master?svg=true)](https://ci.appveyor.com/project/gulrak/filesystem) +[![Build Status](https://api.cirrus-ci.com/github/gulrak/filesystem.svg?branch=master)](https://cirrus-ci.com/github/gulrak/filesystem) +[![Build Status](https://cloud.drone.io/api/badges/gulrak/filesystem/status.svg?ref=refs/heads/master)](https://cloud.drone.io/gulrak/filesystem) +[![Coverage Status](https://coveralls.io/repos/github/gulrak/filesystem/badge.svg?branch=master)](https://coveralls.io/github/gulrak/filesystem?branch=master) +[![Latest Release Tag](https://img.shields.io/github/tag/gulrak/filesystem.svg)](https://github.com/gulrak/filesystem/tree/v1.5.12) + +- [Filesystem](#filesystem) + - [Motivation](#motivation) + - [Why the namespace GHC?](#why-the-namespace-ghc) + - [Platforms](#platforms) + - [Tests](#tests) + - [Usage](#usage) + - [Downloads](#downloads) + - [Using it as Single-File-Header](#using-it-as-single-file-header) + - [Using it as Forwarding-/Implementation-Header](#using-it-as-forwarding-implementation-header) + - [Git Submodule and CMake](#git-submodule-and-cmake) + - [Versioning](#versioning) + - [Documentation](#documentation) + - [`ghc::filesystem::ifstream`, `ghc::filesystem::ofstream`, `ghc::filesystem::fstream`](#ghcfilesystemifstream-ghcfilesystemofstream-ghcfilesystemfstream) + - [`ghc::filesystem::u8arguments`](#ghcfilesystemu8arguments) + - [Differences](#differences) + - [LWG Defects](#lwg-defects) + - [Not Implemented on C++ before C++17](#not-implemented-on-c-before-c17) + - [Differences in API](#differences-in-api) + - [Differences of Specific Interfaces](#differences-of-specific-interfaces) + - [Differences in Behavior](#differences-in-behavior) + - [fs.path](#fspath-refhttpsencppreferencecomwcppfilesystempath) + - [Open Issues](#open-issues) + - [Windows](#windows) + - [Symbolic Links on Windows](#symbolic-links-on-windows) + - [Permissions](#permissions) + - [Release Notes](#release-notes) + +# Filesystem + +This is a header-only single-file `std::filesystem` compatible helper library, +based on the C++17 and C++20 specs, but implemented for C++11, C++14, C++17 or C++20 +(tightly following the C++17 standard with very few documented exceptions). It is currently tested on +macOS 10.12/10.14/10.15/11.6, Windows 10, Ubuntu 18.04, Ubuntu 20.04, CentOS 7, CentOS 8, FreeBSD 12, +Alpine ARM/ARM64 Linux and Solaris 10 but should work on other systems too, as long as you have +at least a C++11 compatible compiler. It should work with Android NDK, Emscripten and I even +had reports of it being used on iOS (within sandboxing constraints) and with v1.5.6 there +is experimental support for QNX. The support of Android NDK, Emscripten and QNX is not +backed up by automated testing but PRs and bug reports are welcome for those too. +It is of course in its own namespace `ghc::filesystem` to not interfere with a regular `std::filesystem` +should you use it in a mixed C++17 environment (which is possible). + +*Test coverage is well above 90%, and starting with v1.3.6 and in v1.5.0 +more time was invested in benchmarking and optimizing parts of the library. I'll try +to continue to optimize some parts and refactor others, striving +to improve it as long as it doesn't introduce additional C++17/C++20 compatibility +issues. Feedback is always welcome. Simply open an issue if you see something missing +or wrong or not behaving as expected and I'll comment.* + + +## Motivation + +I'm often in need of filesystem functionality, mostly `fs::path`, but directory +access too, and when beginning to use C++11, I used that language update +to try to reduce my third-party dependencies. I could drop most of what +I used, but still missed some stuff that I started implementing for the +fun of it. Originally I based these helpers on my own coding- and naming +conventions. When C++17 was finalized, I wanted to use that interface, +but it took a while, to push myself to convert my classes. + +The implementation is closely based on chapter 30.10 from the C++17 standard +and a draft close to that version is +[Working Draft N4687](https://github.com/cplusplus/draft/raw/master/papers/n4687.pdf). +It is from after the standardization of C++17 but it contains the latest filesystem +interface changes compared to the +[Working Draft N4659](https://github.com/cplusplus/draft/raw/master/papers/n4659.pdf). +Staring with v1.4.0, when compiled using C++20, it adapts to the changes according to path sorting order +and `std::u8string` handling from [Working Draft N4860](https://isocpp.org/files/papers/N4860.pdf). + +I want to thank the people working on improving C++, I really liked how the language +evolved with C++11 and the following standards. Keep on the good work! + +## Why the namespace GHC? +If you ask yourself, what `ghc` is standing for, it is simply +`gulraks helper classes`, yeah, I know, not very imaginative, but I wanted a +short namespace and I use it in some of my private classes (so **it has nothing +to do with Haskell**, sorry for the name clash). + +## Platforms + +`ghc::filesystem` is developed on macOS but CI tested on macOS, Windows, +various Linux Distributions, FreeBSD and starting with v1.5.12 on Solaris. +It should work on any of these with a C++11-capable compiler. Also there are some +checks to hopefully better work on Android, but as I currently don't test with the +Android NDK, I wouldn't call it a supported platform yet, same is valid for using +it with Emscripten. It is now part of the detected platforms, I fixed the obvious +issues and ran some tests with it, so it should be fine. All in all, I don't see it +replacing `std::filesystem` where full C++17 or C++20 is available, it doesn't try +to be a "better" `std::filesystem`, just an almost drop-in if you can't use it +(with the exception of the UTF-8 preference). + +:information_source: **Important:** _This implementation is following the ["UTF-8 Everywhere" philosophy](https://utf8everywhere.org/) in that all +`std::string` instances will be interpreted the same as `std::u8string` encoding +wise and as being in UTF-8. The `std::u16string` will be seen as UTF-16. See *Differences in API* +for more information._ + +Unit tests are currently run with: + +* macOS 10.12: Xcode 9.2 (clang-900.0.39.2), GCC 9.2, Clang 9.0, macOS 10.13: Xcode 10.1, macOS 10.14: Xcode 11.2, macOS 10.15: Xcode 11.6, Xcode 12.4 +* Windows: Visual Studio 2017, Visual Studio 2015, Visual Studio 2019, MinGW GCC 6.3 (Win32), GCC 7.2 (Win64), Cygwin GCC 10.2 (no CI yet) +* Linux (Ubuntu): GCC (5.5, 6.5, 7.4, 8.3, 9.2), Clang (5.0, 6.0, 7.1, 8.0, 9.0) +* Linux (Alpine ARM/ARM64): GCC 9.2.0 +* FreeBSD: Clang 8.0 +* Solaris: GCC 5.5 + + +## Tests + +The header comes with a set of unit-tests and uses [CMake](https://cmake.org/) +as a build tool and [Catch2](https://github.com/catchorg/Catch2) as test framework. +All tests are registered with in CMake, so the [ctest](https://cmake.org/cmake/help/latest/manual/ctest.1.html) +commando can be used to run the tests. + +All tests against this implementation should succeed, depending on your environment +it might be that there are some warnings, e.g. if you have no rights to create +Symlinks on Windows or at least the test thinks so, but these are just informative. + +To build the tests from inside the project directory under macOS or Linux just: + +```cpp +mkdir build +cd build +cmake -DCMAKE_BUILD_TYPE=Debug .. +make +ctest +``` + +This generates the test binaries that run the tests and the last command executes +them. + +If the default compiler is a GCC 8 or newer, or Clang 7 or newer, it +additionally tries to build a version of the test binary compiled against GCCs/Clangs +`std::filesystem` implementation, named `std_filesystem_test` +as an additional test of conformance. Ideally all tests should compile and +succeed with all filesystem implementations, but in reality, there are +some differences in behavior, sometimes due to room for interpretation in +in the standard, and there might be issues in these implementations too. + + +## Usage + +### Downloads + +The latest release version is [v1.5.12](https://github.com/gulrak/filesystem/tree/v1.5.12) and +source archives can be found [here](https://github.com/gulrak/filesystem/releases/tag/v1.5.12). + +The latest pre-native-backend version is [v1.4.0](https://github.com/gulrak/filesystem/tree/v1.4.0) and +source archives can be found [here](https://github.com/gulrak/filesystem/releases/tag/v1.4.0). + +The latest pre-C++20-support release version is [v1.3.10](https://github.com/gulrak/filesystem/tree/v1.3.10) and +source archives can be found [here](https://github.com/gulrak/filesystem/releases/tag/v1.3.10). + +Currently only the latest minor release version receives bugfixes, so if possible, +you should use the latest release. + +### Using it as Single-File-Header + +As `ghc::filesystem` is at first a header-only library, it should be enough to copy the header +or the `include/ghc` directory into your project folder or point your include path to this place and +simply include the `filesystem.hpp` header (or `ghc/filesystem.hpp` if you use the subdirectory). + +Everything is in the namespace `ghc::filesystem`, so one way to use it only as +a fallback could be: + +```cpp +#ifdef __APPLE__ +#include // for deployment target to support pre-catalina targets without std::fs +#endif +#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) +#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) +#define GHC_USE_STD_FS +#include +namespace fs = std::filesystem; +#endif +#endif +#ifndef GHC_USE_STD_FS +#include +namespace fs = ghc::filesystem; +#endif +``` + +**Note that this code uses a two-stage preprocessor condition because Visual Studio 2015 +doesn't like the `(<...>)` syntax, even if it could cut evaluation early before. This code also +used the minimum deployment target to detect if `std::filesystem` really is available on macOS +compilation.** + +**Note also, this detection now works on MSVC versions prior to 15.7 on, or without setting +the `/Zc:__cplusplus` compile switch that would fix `__cplusplus` on MSVC. (Without the switch +the compiler always reports `199711L` +([see](https://blogs.msdn.microsoft.com/vcblog/2018/04/09/msvc-now-correctly-reports-__cplusplus/)), +but `_MSVC_LANG` works without it.** + +If you want to also use the `fstream` wrapper with `path` support as fallback, +you might use: + +```cpp +#ifdef __APPLE__ +#include // for deployment target to support pre-catalina targets without std::fs +#endif +#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) +#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) +#define GHC_USE_STD_FS +#include +namespace fs { +using namespace std::filesystem; +using ifstream = std::ifstream; +using ofstream = std::ofstream; +using fstream = std::fstream; +} +#endif +#endif +#ifndef GHC_USE_STD_FS +#include +namespace fs { +using namespace ghc::filesystem; +using ifstream = ghc::filesystem::ifstream; +using ofstream = ghc::filesystem::ofstream; +using fstream = ghc::filesystem::fstream; +} +#endif +``` + +Now you have e.g. `fs::ofstream out(somePath);` and it is either the wrapper or +the C++17 `std::ofstream`. + +:information_source: **Be aware, as a header-only library, it is not hiding the fact, that it +uses system includes, so they "pollute" your global namespace. Use the +forwarding-/implementation-header based approach (see below) to avoid this. +For Windows it needs `Windows.h` and it might be a good idea to define +`WIN32_LEAN_AND_MEAN` or `NOMINMAX` prior to including `filesystem.hpp` or +`fs_std.hpp` headers to reduce pollution of your global namespace and compile +time. They are not defined by `ghc::filesystem` to allow combination with contexts +where the full `Windows.h`is needed, e.g. for UI elements.** + +:information_source: **Hint:** There is an additional header named `ghc/fs_std.hpp` that implements this +dynamic selection of a filesystem implementation, that you can include +instead of `ghc/filesystem.hpp` when you want `std::filesystem` where +available and `ghc::filesystem` where not. + + +### Using it as Forwarding-/Implementation-Header + +Alternatively, starting from v1.1.0 `ghc::filesystem` can also be used by +including one of two additional wrapper headers. These allow to include +a forwarded version in most places (`ghc/fs_fwd.hpp`) while hiding the +implementation details in a single cpp file that includes `ghc/fs_impl.hpp` to +implement the needed code. Using `ghc::filesystem` this way makes sure +system includes are only visible from inside the cpp file, all other places are clean. + +Be aware, that it is currently not supported to hide the implementation +into a Windows-DLL, as a DLL interface with C++ standard templates in interfaces +is a different beast. If someone is willing to give it a try, I might integrate +a PR but currently working on that myself is not a priority. + +If you use the forwarding/implementation approach, you can still use the dynamic +switching like this: + +```cpp +#ifdef __APPLE__ +#include // for deployment target to support pre-catalina targets without std::fs +#endif +#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) +#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) +#define GHC_USE_STD_FS +#include +namespace fs { +using namespace std::filesystem; +using ifstream = std::ifstream; +using ofstream = std::ofstream; +using fstream = std::fstream; +} +#endif +#endif +#ifndef GHC_USE_STD_FS +#include +namespace fs { +using namespace ghc::filesystem; +using ifstream = ghc::filesystem::ifstream; +using ofstream = ghc::filesystem::ofstream; +using fstream = ghc::filesystem::fstream; +} +#endif +``` + +and in the implementation hiding cpp, you might use (before any include that includes `ghc/fs_fwd.hpp` +to take precedence: + +```cpp +#ifdef __APPLE__ // for deployment target to support pre-catalina targets without std::fs +#include +#endif +#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) +#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) +#define GHC_USE_STD_FS +#endif +#endif +#ifndef GHC_USE_STD_FS +#define GHC_FILESYSTEM_IMPLEMENTATION +#include +#endif +``` + +:information_source: **Hint:** There are additional helper headers, named `ghc/fs_std_fwd.hpp` and +`ghc/fs_std_impl.hpp` that use this technique, so you can simply include them +if you want to dynamically select the filesystem implementation. they also +enable the `wchar_t` support on `ghc::filesystem` on Windows, so the resulting +implementation in the `fs` namespace will be compatible. + + + +### Git Submodule and CMake + +Starting from v1.1.0, it is possible to add `ghc::filesystem` +as a git submodule, add the directory to your `CMakeLists.txt` with +`add_subdirectory()` and then simply use `target_link_libraries(your-target ghc_filesystem)` +to ensure correct include path that allow `#include ` +to work. + +The `CMakeLists.txt` offers a few options to customize its behavior: + +* `GHC_FILESYSTEM_BUILD_TESTING` - Compile tests, default is `OFF` when used as + a submodule, else `ON`. +* `GHC_FILESYSTEM_BUILD_EXAMPLES` - Compile the examples, default is `OFF` when used as + a submodule, else `ON`. +* `GHC_FILESYSTEM_WITH_INSTALL` - Add install target to build, default is `OFF` when used as + a submodule, else `ON`. +* `GHC_FILESYSTEM_BUILD_STD_TESTING` - Compile `std_filesystem_test`, the variant of + the test suite running against `std::filesystem`, defaulting to `GHC_FILESYSTEM_BUILD_TESTING`. + This is only done if the compiler is detected as being able to do it. +* `GHC_FILESYSTEM_TEST_COMPILE_FEATURES` can be set to a list of features to override + `CMAKE_CXX_COMPILE_FEATURES` when the detection of C++17 or C++20 for additional tests + is not working (e.g. `cxx_std_20` to enforce building a `filesystem_test_cpp20` with C++20). + +### Versioning + +There is a version macro `GHC_FILESYSTEM_VERSION` defined in case future changes +might make it needed to react on the version, but I don't plan to break anything. +It's the version as decimal number `(major * 10000 + minor * 100 + patch)`. + +:information_source: **Note:** Only even patch versions will be used for releases +and odd patch version will only be used for in between commits while working on +the next version. + + +## Documentation + +There is almost no documentation in this release, as any `std::filesystem` +documentation would work, besides the few differences explained in the next +section. So you might head over to https://en.cppreference.com/w/cpp/filesystem +for a description of the components of this library. + +When compiling with C++11, C++14 or C++17, the API is following the C++17 +standard, where possible, with the exception that `std::string_view` parameters +are only supported on C++17. When Compiling with C++20, `ghc::filesysytem` +defaults to the C++20 API, with the `char8_t` and `std::u8string` interfaces +and the deprecated `fs::u8path` factory method. + +:information_source: **Note:** If the C++17 API should be enforced even in C++20 mode, +use the define `GHC_FILESYSTEM_ENFORCE_CPP17_API`. +Even then it is possible to create `fws::path` from `std::u8string` but +`fs::path::u8string()` and `fs::path::generic_u8string()` return normal +UTF-8 encoded `std::string` instances, so code written for C++17 could +still work with `ghc::filesystem` when compiled with C++20. + +The only additions to the standard are documented here: + + +### `ghc::filesystem::ifstream`, `ghc::filesystem::ofstream`, `ghc::filesystem::fstream` + +These are simple wrappers around `std::ifstream`, `std::ofstream` and `std::fstream`. +They simply add an `open()` method and a constructor with an `ghc::filesystem::path` +argument as the `fstream` variants in C++17 have them. + +### `ghc::filesystem::u8arguments` + +This is a helper class that currently checks for UTF-8 encoding on non-Windows platforms but on Windows it +fetches the command line arguments as Unicode strings from the OS with + +```cpp +::CommandLineToArgvW(::GetCommandLineW(), &argc) +``` + +and then converts them to UTF-8, and replaces `argc` and `argv`. It is a guard-like +class that reverts its changes when going out of scope. + +So basic usage is: + +```cpp +namespace fs = ghc::filesystem; + +int main(int argc, char* argv[]) +{ + fs::u8arguments u8guard(argc, argv); + if(!u8guard.valid()) { + std::cerr << "Bad encoding, needs UTF-8." << std::endl; + exit(EXIT_FAILURE); + } + + // now use argc/argv as usual, they have utf-8 enconding on windows + // ... + + return 0; +} +``` + +That way `argv` is UTF-8 encoded as long as the scope from `main` is valid. + +**Note:** On macOS, while debugging under Xcode the code currently will return +`false` as Xcode starts the application with `US-ASCII` as encoding, no matter what +encoding is actually used and even setting `LC_ALL` in the product scheme doesn't +change anything. I still need to investigate this. + + +## Differences + +As this implementation is based on existing code from my private helper +classes, it derived some constraints of it. Starting from v1.5.0 most of the +differences between this and the standard C++17/C++20 API where removed. + + +### LWG Defects + +This implementation has switchable behavior for the LWG defects +[#2682](https://wg21.cmeerw.net/lwg/issue2682), +[#2935](http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2935), +[#2936](http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2936) and +[#2937](http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2937). +The currently selected behavior (starting from v1.4.0) is following +[#2682](https://wg21.cmeerw.net/lwg/issue2682), [#2936](http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2936), +[#2937](http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2937) but +not following [#2935](http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2935), +as I feel it is a bug to report no error on a `create_directory()` or `create_directories()` +where a regular file of the same name prohibits the creation of a directory and forces +the user of those functions to double-check via `fs::is_directory` if it really worked. +The more intuitive approach to directory creation of treating a file with that name as an +error is also advocated by the newer paper +[WG21 P1164R0](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1164r0.pdf), the revision +P1161R1 was agreed upon on Kona 2019 meeting [see merge](https://github.com/cplusplus/draft/issues/2703) +and GCC by now switched to following its proposal +([GCC #86910](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86910)). + +### Not Implemented on C++ before C++17 + +```cpp +// methods in ghc::filesystem::path: +path& operator+=(basic_string_view x); +int compare(basic_string_view s) const; +``` + +These are not implemented under C++11 and C++14, as there is no +`std::basic_string_view` available and I did want to keep this +implementation self-contained and not write a full C++17-upgrade for +C++11/14. Starting with v1.1.0 these are supported when compiling +`ghc::filesystem` under C++17 of C++20. + +Starting with v1.5.2 `ghc::filesystem` will try to allow the use of +`std::experimental::basic_string_view` where it detects is availability. +Additionally if you have a `basic_string_view` compatible c++11 +implementation it can be used instead of `std::basic_string_view` +by defining `GHC_HAS_CUSTOM_STRING_VIEW` and importing the +implementation into the `ghc::filesystem` namespace with: + +```cpp +namespace ghc { + namespace filesystem { + using my::basic_string_view; + } +} +``` + +before including the filesystem header. + +### Differences in API + +To not depend on any external third party libraries and still stay portable and +compact, this implementation is following the ["UTF-8 Everywhere" philosophy](https://utf8everywhere.org/) in that all +`std::string` instances will be interpreted the same as `std::u8string` encoding +wise and as being in UTF-8. The `std::u16string` will be seen as UTF-16 and `std::u32string` will be +seen as Unicode codepoints. Depending on the size of `std::wstring` characters, it will handle +`std::wstring` as being UTF-16 (e.g. Windows) or `char32_t` Unicode codepoints +(currently all other platforms). + +#### Differences of Specific Interfaces + +Starting with v1.5.0 `ghc::filesystem` is following the C++17 standard in +using `wchar_t` and `std::wstring` on Windows as the types internally used +for path representation. It is still possible to get the old behavior by defining +`GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE` and get `filesystem::path::string_type` as +`std::string` and `filesystem::path::value_type` as `wchar_t`. + +If you need to call some Windows API, with v1.5.0 and above, simply +use the W-variant of the Windows-API call (e.g. `GetFileAttributesW(p.c_str())`). + +:information_source: **Note:** _When using the old behavior by defining +`GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE`, use the `path::wstring()` member +(e.g. `GetFileAttributesW(p.wstring().c_str())`). This gives you the +Unicode variant independent of the `UNICODE` macro and makes sharing code +between Windows, Linux and macOS easier and works with `std::filesystem` and +`ghc::filesystem`._ + +```cpp +std::string path::u8string() const; +std::string path::generic_u8string() const; +vs. +std::u8string path::u8string() const; +std::u8string path::generic_u8string() const; +``` + +The return type of these two methods is depending on the used C++ standard +and if `GHC_FILESYSTEM_ENFORCE_CPP17_API` is defined. On C++11, C++14 and +C++17 or when `GHC_FILESYSTEM_ENFORCE_CPP17_API` is defined, the return +type is `std::string`, and on C++20 without the define it is `std::u8string`. + +### Differences in Behavior + +I created a wiki entry about quite a lot of [behavioral differences](https://github.com/gulrak/filesystem/wiki/Differences-to-Standard-Filesystem-Implementations) +between different `std::filesystem` implementations that could result in a +mention here, but this readme only tries to address the design choice +differences between `ghc::filesystem` and those. I try to update the wiki page +from time to time. + +Any additional observations are welcome! + +#### fs.path ([ref](https://en.cppreference.com/w/cpp/filesystem/path)) + +Since v1.5.0 the complete inner mechanics of this implementations `fs::path` +where changed to the _native_ format as the internal representation. +Creating any mixed slash `fs::path` object under Windows (e.g. with `"C:\foo/bar"`) +will lead clean path with `"C:\foo\bar"` via `native()` and `"C:/foo/bar"` via +`generic_string()` API. On all platforms redundant additional separators are +removed, even if this is not enforced by the standard and other implementations +mostly not do this. + +Additionally this implementation follows the standards suggestion to handle +posix paths of the form `"//host/path"` and USC path on windows also as having +a root-name (e.g. `"//host"`). The GCC implementation didn't choose to do that +while testing on Ubuntu 18.04 and macOS with GCC 8.1.0 or Clang 7.0.0. This difference +will show as warnings under `std::filesystem`. This leads to a change in the +algorithm described in the standard for `operator/=(path& p)` where any path +`p` with `p.is_absolute()` will degrade to an assignment, while this implementation +has the exception where `*this == *this.root_name()` and `p == preferred_separator` +a normal append will be done, to allow: + +```cpp +fs::path p1 = "//host/foo/bar/file.txt"; +fs::path p2; +for (auto p : p1) p2 /= p; +ASSERT(p1 == p2); +``` + +For all non-host-leading paths the behavior will match the one described by +the standard. + + +## Open Issues + +### Windows + +#### Symbolic Links on Windows + +As symbolic links on Windows, while being supported more or less since +Windows Vista (with some strict security constraints) and fully since some earlier +build of Windows 10, when "Developer Mode" is activated, are at time of writing +(2018) rarely used, still they are supported wiit th this implementation. + +#### Permissions + +The Windows ACL permission feature translates badly to the POSIX permission +bit mask used in the interface of C++17 filesystem. The permissions returned +in the `file_status` are therefore currently synthesized for the `user`-level +and copied to the `group`- and `other`-level. There is still some potential +for more interaction with the Windows permission system, but currently setting +or reading permissions with this implementation will most certainly not lead +to the expected behavior. + + +## Release Notes + +### [v1.5.12](https://github.com/gulrak/filesystem/releases/tag/v1.5.12) + +* Fix for [#142](https://github.com/gulrak/filesystem/issues/142), removed need + for `GHC_NO_DIRENT_D_TYPE` on systems that don't support `dirent::d_type` and + fixed build configuration and tests to support Solaris as new platform. +* Pull request [#138](https://github.com/gulrak/filesystem/pull/138), if the + platform uses the POSIX backend and has no `PATH_MAX`, one is defined. +* Pull request [#137](https://github.com/gulrak/filesystem/pull/137), update + of Catch2 to version v2.13.7 +* Added macOS 11 to the automatically tested platforms. + +### [v1.5.10](https://github.com/gulrak/filesystem/releases/tag/v1.5.10) + +* Pull request [#136](https://github.com/gulrak/filesystem/pull/136), the Windows + implementation used some unnecessary expensive shared pointer for resource + management and these where replaced by a dedicated code. +* Fix for [#132](https://github.com/gulrak/filesystem/issues/132), pull request + [#135](https://github.com/gulrak/filesystem/pull/135), `fs::remove_all` now + just deletes symbolic links instead of following them. +* Pull request [#133](https://github.com/gulrak/filesystem/pull/133), fix for + `fs::space` where a numerical overflow could happen in a multiplication. +* Replaced _travis-ci.org_ with GitHub Workflow for the configurations: + Ubuntu 20.04: GCC 9.3, Ubuntu 18.04: GCC 7.5, GCC 8.4, macOS 10.15: Xcode 12.4, + Windows 10: Visual Studio 2019 + +### [v1.5.8](https://github.com/gulrak/filesystem/releases/tag/v1.5.8) + +* Fix for [#125](https://github.com/gulrak/filesystem/issues/124), where + `fs::create_directories` on Windows no longer breaks on long filenames. + +### [v1.5.6](https://github.com/gulrak/filesystem/releases/tag/v1.5.6) + +* Fix for [#124](https://github.com/gulrak/filesystem/issues/124), + `ghc::filesystem` treated mounted folder/volumes erroneously as symlinks, + leading `fs::canonical` to fail on paths containing those. +* Fix for [#122](https://github.com/gulrak/filesystem/issues/122), incrementing + the `recursive_directory_iterator` will not try to enter dead symlinks. +* Fix for [#121](https://github.com/gulrak/filesystem/issues/121), on Windows + backend the `fs::remove` failed when the path pointed to a read-only entry, + see also ([microsoft/STL#1511](https://github.com/microsoft/STL/issues/1511)) + for the corresponding issue in `std::fs` on windows. +* Fix for [#119](https://github.com/gulrak/filesystem/issues/119), added missing + support for char16_t and char32_t and on C++20 char8_t literals. +* Pull request [#118](https://github.com/gulrak/filesystem/pull/118), when + running tests as root, disable tests that would not work. +* Pull request [#117](https://github.com/gulrak/filesystem/pull/117), added + checks to tests to detect the clang/libstdc++ combination. +* Fix for [#116](https://github.com/gulrak/filesystem/issues/116), internal + macro `GHC_NO_DIRENT_D_TYPE` allows os detection to support systems without + the `dirent.d_type` member, experimental first QNX compile support as + initial use case, fixed issue with filesystems returning DT_UNKNOWN + (e.g. reiserfs). +* Pull request [#115](https://github.com/gulrak/filesystem/pull/115), added + `string_view` support when clang with libstdc++ is detected. +* Fix for [#114](https://github.com/gulrak/filesystem/issues/114), for macOS + the pre-Catalina deployment target detection worked only if `` + was included before `` or ``/``. +* Fix for [#113](https://github.com/gulrak/filesystem/issues/113), the use of + standard chapter numbers was misleading since C++17 and C++20 `std::filesystem` + features are supported, and was replaced by the tag-like chapter names that + stay (mostly) consistent over the versions. + +### [v1.5.4](https://github.com/gulrak/filesystem/releases/tag/v1.5.4) + +* Pull request [#112](https://github.com/gulrak/filesystem/pull/112), lots + of cleanup work on the readme, thanks! +* Enhancement for [#111](https://github.com/gulrak/filesystem/issues/111), + further optimization of directory iteration, performance for + `recursive_directory_iterator` over large trees now somewhere between + libc++ and libstdc++. +* Enhancement for [#110](https://github.com/gulrak/filesystem/issues/110), + `ghc::filesystem` now has preliminary support for Cygwin. Changes where + made to allow the tests to compile and run successfully (tested with GCC + 10.2.0), feedback and additional PRs welcome as it is currently not + part of the CI configuration. +* Pull request [#109](https://github.com/gulrak/filesystem/pull/109), various + spelling errors in error messages and comments fixed. +* Pull request [#108](https://github.com/gulrak/filesystem/pull/108), old + style casts removed. +* Fix for [#107](https://github.com/gulrak/filesystem/issues/107), the error + handling for status calls was suppressing errors on symlink targets. +* Pull request [#106](https://github.com/gulrak/filesystem/pull/106), fixed + detection of AppleClang for compile options. +* Pull request [#105](https://github.com/gulrak/filesystem/pull/105), added + option `GHC_FILESYSTEM_BUILD_STD_TESTING` to override additional build of + `std::filesystem` versions of the tests for comparison and the possibility + to use `GHC_FILESYSTEM_TEST_COMPILE_FEATURES` to prefill the used compile + features defaulting to `CMAKE_CXX_COMPILE_FEATURES` when not given. + +### [v1.5.2](https://github.com/gulrak/filesystem/releases/tag/v1.5.2) + +* Enhancement [#104](https://github.com/gulrak/filesystem/issues/104), + on POSIX backend: optimized reuse of status information and reduced + `directory_entry` creation leads to about 20%-25% in tests with + `recursive_directory_iterator` over a larger directory tree. +* Pull request [#103](https://github.com/gulrak/filesystem/pull/103), `wchar_t` + was not in the list of supported char types on non-Windows backends. +* Pull request [#102](https://github.com/gulrak/filesystem/pull/102), improved + `string_view` support makes use of `` or `` + when available, and allows use of custom `basic_string_view` implementation + when defining `GHC_HAS_CUSTOM_STRING_VIEW` and importing the string view + into the `ghc::filesystem` namespace before including filesystem header. +* Pull request [#101](https://github.com/gulrak/filesystem/pull/101), fix for + [#100](https://github.com/gulrak/filesystem/issues/100), append and concat + type of operations on path called redundant conversions. +* Pull request [#98](https://github.com/gulrak/filesystem/pull/98), on older + linux variants (GCC 7/8), the comparison `std::filesystem` tests now link + with `-lrt` to avoid issues. +* Fix for [#97](https://github.com/gulrak/filesystem/issues/97), on BTRFS the + test case for `fs::hard_link_count` failed due to the filesystems behavior, + the test case was adapted to take that into account. +* Pull request [#96](https://github.com/gulrak/filesystem/pull/96), the export + attribute defines `GHC_FS_API` and `GHC_FS_API_CLASS` are now honored when when + set from outside to allow override of behavior. +* Fix for [#95](https://github.com/gulrak/filesystem/issues/95), the syntax for + disabling the deprecated warning in tests in MSVC was wrong. +* Pull request [#93](https://github.com/gulrak/filesystem/pull/93), now the + CMake configuration file is configured and part of the `make install` files. + +### [v1.5.0](https://github.com/gulrak/filesystem/releases/tag/v1.5.0) + +* Fix for [#91](https://github.com/gulrak/filesystem/issues/91), the way + the CMake build options `GHC_FILESYSTEM_BUILD_TESTING`, `GHC_FILESYSTEM_BUILD_EXAMPLES` + and `GHC_FILESYSTEM_WITH_INSTALL` where implemented, prohibited setting them + from a parent project when using this via `add_subdirectory`, this fix + allows to set them again. +* Major refactoring for [#90](https://github.com/gulrak/filesystem/issues/90), + the way, the Windows version of `fs::path` was originally created from the + POSIX based implementation was, by adaption of the incoming and outgoing + strings. This resulted in a mutable cache inside `fs::path`on Windows, that + was inherently not thread-safe, even for `const` methods. + To not add additional patches to a suboptimal solution, this time I reworked + the `path` code to now store _native_ path-representation. This changed a + lot of code, but when combined with `wchar_t` as `value_type` helped to avoid + lots of conversion for calls to Win-API.
+ As interfaces where changed, it had to be released in a new minor version. + The set of refactorings resulted in the following changes: + * `fs::path::native()` and `fs::path::c_str()` can now be `noexcept` as the + standard mandates + * On Windows `wchar_t` is now the default for `fs::path::value_type` and + `std::wstring` is the default for `fs::path::string_type`. + * This allows the implementation to call Win-API without allocating + conversions + * Thread-safety on `const` methods of `fs::path` is no longer an issue + * Some code could be simplified during this refactoring + * Automatic prefixing of long path on Windows can now be disabled with + defining `GHC_WIN_DISABLE_AUTO_PREFIXES`, for all other types of prefixes + or namespaces the behavior follows that of MSVC `std::filesystem::path` + * In case the old `char`/`std::string` based approach for Windows is still + needed, it can be activated with `GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE` +* Enhancement for [#89](https://github.com/gulrak/filesystem/issues/89), `fs::file_status` + now supports `operator==` introduced in `std::filesystem` with C++20. +* Refactoring for [#88](https://github.com/gulrak/filesystem/issues/88), `fs::path::parent_path()` + had a performance issue, as it was still using a loop based approach to recreate + the parent from elements. This created lots of temporaries and was too slow + especially on long paths. + +### [v1.4.0](https://github.com/gulrak/filesystem/releases/tag/v1.4.0) + +* Enhancements for [#71](https://github.com/gulrak/filesystem/issues/71), when compiled with C++20: + * `char8_t` and `std::u8string` are supported where `Source` is the parameter type + * `fs::path::u8string()` and `fs::path::generic_u8string()` now return a `std::u8string` + * The _spaceship operator_ `<=>` is now supported for `fs::path` + * With the define `GHC_FILESYSTEM_ENFORCE_CPP17_API` `ghc::filesystem` will fall back + to the old `fs::path::u8string()` and `fs::path::generic_u8string()` API if preferred +* Bugfix for `fs::proximate(p, ec)` where the internal call to `fs::current_path()` was not + using the `error_code` variant, throwing possible exceptions instead of setting `ec`. +* Enhancement `LWG_2936_BEHAVIOUR` is now on by default. +* Some cleanup work to reduce preprocessor directives for better readability and remove unneeded + template specializations. + +### [v1.3.10](https://github.com/gulrak/filesystem/releases/tag/v1.3.10) + +* Fix for [#81](https://github.com/gulrak/filesystem/issues/81), fixed issues with + handling `Source` parameters that are string views. +* Fix for [#79](https://github.com/gulrak/filesystem/issues/79), the bit operations + for filesystem bitmasks that should be are now `constexpr`. + +### [v1.3.8](https://github.com/gulrak/filesystem/releases/tag/v1.3.8) + +* Refactoring for [#78](https://github.com/gulrak/filesystem/issues/78), the dynamic + switching helper includes are now using `__MAC_OS_X_VERSION_MIN_REQUIRED` to + ensure that `std::filesystem` is only selected on macOS if the deployment target is + at least Catalina. +* Bugfix for [#77](https://github.com/gulrak/filesystem/issues/77), the `directory_iterator` + and the `recursive_directory_iterator` had an issue with the `skip_permission_denied` + option, that leads to the inability to skip SIP protected folders on macOS. +* Enhancement for [#76](https://github.com/gulrak/filesystem/issues/76), `_MSVC_LANG` is + now used when available, additionally to `__cplusplus`, in the helping headers to + allow them to work even when `/Zc:__cplusplus` is not used. +* Bugfix for [#75](https://github.com/gulrak/filesystem/issues/75), NTFS reparse points + to mapped volumes where handled incorrect, leading to `false` on `fs::exists` or + not-found-errors on `fs::status`. Namespaced paths are not filtered anymore. + +### [v1.3.6](https://github.com/gulrak/filesystem/releases/tag/v1.3.6) + +* Pull request [#74](https://github.com/gulrak/filesystem/pull/74), on Windows symlink + evaluation used the wrong reparse struct information and was not handling the case + of relative paths well, thanks for the contribution. +* Refactoring for [#73](https://github.com/gulrak/filesystem/issues/73), enhanced performance + in path handling. the changes lead to much fewer path/string creations or copies, speeding + up large directory iteration or operations on many path instances. +* Bugfix for [#72](https://github.com/gulrak/filesystem/issues/72), the `TestAllocator` in + `filesystem_test.cpp` was completed to fulfill the requirements to build on CentOS 7 with + `devtoolset-9`. CentOS 7 and CentOS 8 are now part of the CI builds. +* Bugfix for [#70](https://github.com/gulrak/filesystem/issues/70), root names are now case + insensitive on Windows. This fix also adds the new behavior switch `LWG_2936_BEHAVIOUR` + that allows to enable post C++17 `fs::path::compare` behavior, where the comparison is as + if it was an element wise path comparison as described in + [LWG 2936](https://cplusplus.github.io/LWG/issue2936) and C++20 `[fs.path.compare]`. + It is default off in v1.3.6 and will be default starting from v1.4.0 as it changes ordering. + +### [v1.3.4](https://github.com/gulrak/filesystem/releases/tag/v1.3.4) + +* Pull request [#69](https://github.com/gulrak/filesystem/pull/69), use `wchar_t` versions of + `std::fstream` from `ghc::filesystem::fstream` wrappers on Windows if using GCC with libc++. +* Bugfix for [#68](https://github.com/gulrak/filesystem/issues/68), better handling of + permission issues for directory iterators when using `fs::directory_options::skip_permission_denied` + and initial support for compilation with emscripten. +* Refactoring for [#66](https://github.com/gulrak/filesystem/issues/63), unneeded shared_ptr guards + where removed and the file handles closed where needed to avoid unnecessary allocations. +* Bugfix for [#63](https://github.com/gulrak/filesystem/issues/63), fixed issues on Windows + with clang++ and C++17. +* Pull request [#62](https://github.com/gulrak/filesystem/pull/62), various fixes for + better Android support, thanks for the PR +* Pull request [#61](https://github.com/gulrak/filesystem/pull/61), `ghc::filesystem` now + supports use in projects with disabled exceptions. API signatures using exceptions for + error handling are not available in this mode, thanks for the PR (this resolves + [#60](https://github.com/gulrak/filesystem/issues/60) and + [#43](https://github.com/gulrak/filesystem/issues/43)) + +### [v1.3.2](https://github.com/gulrak/filesystem/releases/tag/v1.3.2) + +* Bugfix for [#58](https://github.com/gulrak/filesystem/issues/58), on MinGW the + compilation could fail with an error about an undefined `ERROR_FILE_TOO_LARGE` + constant. +* Bugfix for [#56](https://github.com/gulrak/filesystem/issues/58), `fs::lexically_relative` + didn't ignore trailing slash on the base parameter, thanks for PR + [#57](https://github.com/gulrak/filesystem/pull/57). +* Bugfix for [#55](https://github.com/gulrak/filesystem/issues/55), `fs::create_directories` + returned `true` when nothing needed to be created, because the directory already existed. +* Bugfix for [#54](https://github.com/gulrak/filesystem/issues/54), `error_code` + was not reset, if cached result was returned. +* Pull request [#53](https://github.com/gulrak/filesystem/pull/53), fix for wrong + handling of leading whitespace when reading `fs::path` from a stream. +* Pull request [#52](https://github.com/gulrak/filesystem/pull/52), an ARM Linux + target is now part of the CI infrastructure with the service of Drone CI. +* Pull request [#51](https://github.com/gulrak/filesystem/pull/51), FreeBSD is now + part of the CI infrastructure with the service of Cirrus CI. +* Pull request [#50](https://github.com/gulrak/filesystem/pull/50), adaptive cast to + `timespec` fields to avoid warnings. + +### [v1.3.0](https://github.com/gulrak/filesystem/releases/tag/v1.3.0) + +* **Important: `ghc::filesystem` is re-licensed from BSD-3-Clause to MIT license.** (see + [#47](https://github.com/gulrak/filesystem/issues/47)) +* Pull request [#46](https://github.com/gulrak/filesystem/pull/46), suppresses + unused parameter warning on Android. +* Bugfix for [#44](https://github.com/gulrak/filesystem/issues/44), fixes + for warnings from newer Xcode versions. + +### [v1.2.10](https://github.com/gulrak/filesystem/releases/tag/v1.2.10) + +* The Visual Studio 2019 compiler, GCC 9.2 and Clang 9.0 where added to the + CI configuration. +* Bugfix for [#41](https://github.com/gulrak/filesystem/issues/41), `fs::rename` + on Windows didn't replace an existing regular file as required by the standard, + but gave an error. New tests and a fix as provided in the issue was implemented. +* Bugfix for [#39](https://github.com/gulrak/filesystem/issues/39), for the + forwarding use via `fs_fwd.hpp` or `fs_std_fwd.hpp` there was a use of + `DWORD` in the forwarding part leading to an error if `Windows.h` was not + included before the header. The tests were changed to give an error in that + case too and the useage of `DWORD` was removed. +* Bugfix for [#38](https://github.com/gulrak/filesystem/issues/38), casting the + return value of `GetProcAddress` gave a warning with `-Wcast-function-type` + on MSYS2 and MinGW GCC 9 builds. + +### [v1.2.8](https://github.com/gulrak/filesystem/releases/tag/v1.2.8) + +* Pull request [#30](https://github.com/gulrak/filesystem/pull/30), the + `CMakeLists.txt` will automatically exclude building examples and tests when + used as submodule, the configuration options now use a prefixed name to + reduce risk of conflicts. +* Pull request [#24](https://github.com/gulrak/filesystem/pull/24), install + target now creates a `ghcFilesystemConfig.cmake` in + `${CMAKE_INSTALL_LIBDIR}/cmake/ghcFilesystem` for `find_package` that + exports a target as `ghcFilesystem::ghc_filesystem`. +* Pull request [#31](https://github.com/gulrak/filesystem/pull/31), fixes + `error: redundant redeclaration of 'constexpr' static data member` deprecation + warning in C++17 mode. +* Pull request [#32](https://github.com/gulrak/filesystem/pull/32), fixes + old-style-cast warnings. +* Pull request [#34](https://github.com/gulrak/filesystem/pull/34), fixes + [TOCTOU](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use) situation + on `fs::create_directories`, thanks for the PR! +* Feature [#35](https://github.com/gulrak/filesystem/issues/35), new CMake + option to add an install target `GHC_FILESYSTEM_WITH_INSTALL` that is + defaulted to OFF if `ghc::filesystem` is used via `add_subdirectory`. +* Bugfix for [#33](https://github.com/gulrak/filesystem/issues/33), fixes + an issue with `fs::path::lexically_normal()` that leaves a trailing separator + in case of a resulting path ending with `..` as last element. +* Bugfix for [#36](https://github.com/gulrak/filesystem/issues/36), warnings + on Xcode 11.2 due to unhelpful references in path element iteration. + +### [v1.2.6](https://github.com/gulrak/filesystem/releases/tag/v1.2.6) + +* Pull request [#23](https://github.com/gulrak/filesystem/pull/23), tests and + examples can now be disabled in CMake via setting `BUILD_TESTING` and + `BUILD_EXAMPLES` to `NO`, `OFF` or `FALSE`. +* Pull request [#25](https://github.com/gulrak/filesystem/pull/25), + missing specialization for construction from `std::string_view` when + available was added. +* Additional test case when `std::string_view` is available. +* Bugfix for [#27](https://github.com/gulrak/filesystem/issues/27), the + `fs::path::preferred_separator` declaration was not compiling on pre + C++17 compilers and no test accessed it, to show the problem. Fixed + it to an construction C++11 compiler should accept and added a test that + is successful on all combinations tested. +* Bugfix for [#29](https://github.com/gulrak/filesystem/issues/29), stricter + warning settings where chosen and resulting warnings where fixed. + +### [v1.2.4](https://github.com/gulrak/filesystem/releases/tag/v1.2.4) + +* Enabled stronger warning switches and resulting fixed issues on GCC and MinGW +* Bugfix for #22, the `fs::copy_options` where not forwarded from `fs::copy` to + `fs::copy_file` in one of the cases. + +### [v1.2.2](https://github.com/gulrak/filesystem/releases/tag/v1.2.2) + +* Fix for ([#21](https://github.com/gulrak/filesystem/pull/21)), when compiling + on Alpine Linux with musl instead of glibc, the wrong `strerror_r` signature + was expected. The complex preprocessor define mix was dropped in favor of + the usual dispatch by overloading a unifying wrapper. + +### [v1.2.0](https://github.com/gulrak/filesystem/releases/tag/v1.2.0) + +* Added MinGW 32/64 and Visual Studio 2015 builds to the CI configuration. +* Fixed additional compilation issues on MinGW. +* Pull request ([#13](https://github.com/gulrak/filesystem/pull/13)), set + minimum required CMake version to 3.7.2 (as in Debian 8). +* Pull request ([#14](https://github.com/gulrak/filesystem/pull/14)), added + support for a make install target. +* Bugfix for ([#15](https://github.com/gulrak/filesystem/issues/15)), the + forward/impl way of using `ghc::filesystem` missed a `` include + in the windows case. +* Bugfix for ([#16](https://github.com/gulrak/filesystem/issues/16)), + VS2019 didn't like the old size dispatching in the utf8 decoder, so it + was changed to a sfinae based approach. +* New feature ([#17](https://github.com/gulrak/filesystem/issues/17)), optional + support for standard conforming `wchar_t/std::wstring` interface when + compiling on Windows with defined `GHC_WIN_WSTRING_STRING_TYPE`, this is + default when using the `ghc/fs_std*.hpp` header, to enhance compatibility. +* New feature ([#18](https://github.com/gulrak/filesystem/issues/18)), optional + filesystem exceptions/errors on Unicode errors with defined + `GHC_RAISE_UNICODE_ERRORS` (instead of replacing invalid code points or + UTF-8 encoding errors with the replacement character `U+FFFD`). +* Pull request ([#20](https://github.com/gulrak/filesystem/pull/20)), fix for + file handle leak in `fs::copy_file`. +* Coverage now checked in CI (~95% line coverage). + +### [v1.1.4](https://github.com/gulrak/filesystem/releases/tag/v1.1.4) + +* Additional Bugfix for ([#12](https://github.com/gulrak/filesystem/issues/12)), + error in old unified `readdir/readdir_r` code of `fs::directory_iterator`; + as `readdir_r` is now deprecated, I decided to drop it and the resulting + code is much easier, shorter and due to more refactoring faster +* Fix for crashing unit tests against MSVC C++17 `std::filesystem` +* Travis-CI now additionally test with Xcode 10.2 on macOS +* Some minor refactorings + +### [v1.1.2](https://github.com/gulrak/filesystem/releases/tag/v1.1.2) + +* Bugfix for ([#11](https://github.com/gulrak/filesystem/issues/11)), + `fs::path::lexically_normal()` had some issues with `".."`-sequences. +* Bugfix for ([#12](https://github.com/gulrak/filesystem/issues/12)), + `fs::recursive_directory_iterator` could run into endless loops, + the methods depth() and pop() had issues and the copy behavior and + `input_iterator_tag` conformance was broken, added tests +* Restructured some CMake code into a macro to ease the support for + C++17 `std::filesystem` builds of tests and examples for interoperability + checks. +* Some fixes on Windows tests to ease interoperability test runs. +* Reduced noise on `fs::weakly_canonical()` tests against `std::fs` +* Added simple `du` example showing the `recursive_directory_iterator` + used to add the sizes of files in a directory tree. +* Added error checking in `fs::file_time_type` test helpers +* `fs::copy()` now conforms LWG #2682, disallowing the use of + `copy_option::create_symlinks' to be used on directories + +### [v1.1.0](https://github.com/gulrak/filesystem/releases/tag/v1.1.0) + +* Restructuring of the project directory. The header files are now using + `hpp` as extension to be marked as c++ and they where moved to + `include/ghc/` to be able to include by `` as the + former include name might have been to generic and conflict with other + files. +* Better CMake support: `ghc::filesystem` now can be used as a submodul + and added with `add_subdirectory` and will export itself as `ghc_filesystem` + target. To use it, only `target_link_libraries(your-target ghc_filesystem)` + is needed and the include directories will be set so `#include ` + will be a valid directive. + Still you can simply only add the header file to you project and include it + from there. +* Enhancement ([#10](https://github.com/gulrak/filesystem/issues/10)), + support for separation of implementation and forwarded api: Two + additional simple includes are added, that can be used to forward + `ghc::filesystem` declarations (`fs_fwd.hpp`) and to wrap the + implementation into a single cpp (`fs_impl.hpp`) +* The `std::basic_string_view` variants of the `fs::path` api are + now supported when compiling with C++17. +* Added CI integration for Travis-CI and Appveyor. +* Fixed MinGW compilation issues. +* Added long filename support for Windows. + +### [v1.0.10](https://github.com/gulrak/filesystem/releases/tag/v1.0.10) + +* Bugfix for ([#9](https://github.com/gulrak/filesystem/issues/9)), added + missing return statement to `ghc::filesystem::path::generic_string()` +* Added checks to hopefully better compile against Android NDK. There where + no tests run yet, so feedback is needed to actually call this supported. +* `filesystem.h` was renamed `filesystem.hpp` to better reflect that it is + a c++ language header. + +### [v1.0.8](https://github.com/gulrak/filesystem/releases/tag/v1.0.8) + +* Bugfix for ([#6](https://github.com/gulrak/filesystem/issues/6)), where + `ghc::filesystem::remove()` and `ghc::filesystem::remove_all()` both are + now able to remove a single file and both will not raise an error if the + path doesn't exist. +* Merged pull request ([#7](https://github.com/gulrak/filesystem/pull/7)), + a typo leading to setting error code instead of comparing it in + `ghc::filesystem::remove()` under Windows. +* Bugfix for (([#8](https://github.com/gulrak/filesystem/issues/8)), the + Windows version of `ghc::filesystem::directory_iterator` now releases + resources when reaching `end()` like the POSIX one does. + + +### [v1.0.6](https://github.com/gulrak/filesystem/releases/tag/v1.0.6) + +* Bugfix for ([#4](https://github.com/gulrak/filesystem/issues/4)), missing error_code + propagation in `ghc::filesystem::copy()` and `ghc::filesystem::remove_all` fixed. +* Bugfix for ([#5](https://github.com/gulrak/filesystem/issues/5)), added missing std + namespace in `ghc::filesystem::recursive_directory_iterator::difference_type`. + +### [v1.0.4](https://github.com/gulrak/filesystem/releases/tag/v1.0.4) + +* Bugfix for ([#3](https://github.com/gulrak/filesystem/issues/3)), fixed missing inlines + and added test to ensure including into multiple implementation files works as expected. +* Building tests with `-Wall -Wextra -Werror` and fixed resulting issues. + +### [v1.0.2](https://github.com/gulrak/filesystem/releases/tag/v1.0.2) + +* Updated catch2 to v2.4.0. +* Refactored `fs.op.permissions` test to work with all tested `std::filesystem` + implementations (gcc, clang, msvc++). +* Added helper class `ghc::filesystem::u8arguments` as `argv` converter, to + help follow the UTF-8 path on windows. Simply instantiate it with `argc` and + `argv` and it will fetch the Unicode version of the command line and convert + it to UTF-8. The destructor reverts the change. +* Added `examples` folder with hopefully some usefull example usage. Examples are + tested (and build) with `ghc::filesystem` and C++17 `std::filesystem` when + available. +* Starting with this version, only even patch level versions will be tagged and + odd patch levels mark in-between non-stable wip states. +* Tests can now also be run against MS version of `std::filesystem` for comparison. +* Added missing `fstream` include. +* Removed non-conforming C99 `timespec`/`timeval` usage. +* Fixed some integer type mismatches that could lead to warnings. +* Fixed `chrono` conversion issues in test and example on clang 7.0.0. + +### [v1.0.1](https://github.com/gulrak/filesystem/releases/tag/v1.0.1) + +* Bugfix: `ghc::filesystem::canonical` now sees empty path as non-existant and reports + an error. Due to this `ghc::filesystem::weakly_canonical` now returns relative + paths for non-existant argument paths. ([#1](https://github.com/gulrak/filesystem/issues/1)) +* Bugfix: `ghc::filesystem::remove_all` now also counts directories removed ([#2](https://github.com/gulrak/filesystem/issues/2)) +* Bugfix: `recursive_directory_iterator` tests didn't respect equality domain issues + and dereferencapable constraints, leading to fails on `std::filesystem` tests. +* Bugfix: Some `noexcept` tagged methods and functions could indirectly throw exceptions + due to UFT-8 decoding issues. +* `std_filesystem_test` is now also generated if LLVM/clang 7.0.0 is found. + + +### [v1.0.0](https://github.com/gulrak/filesystem/releases/tag/v1.0.0) + +This was the first public release version. It implements the full range of +C++17 `std::filesystem`, as far as possible without other C++17 dependencies. + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/filesystem.hpp b/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/filesystem.hpp new file mode 100644 index 000000000..45f2c03b4 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/filesystem.hpp @@ -0,0 +1,6065 @@ +//--------------------------------------------------------------------------------------- +// +// ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14/C++17/C++20 +// +//--------------------------------------------------------------------------------------- +// +// Copyright (c) 2018, Steffen Schümann +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +//--------------------------------------------------------------------------------------- +// +// To dynamically select std::filesystem where available on most platforms, +// you could use: +// +// #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) +// #if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) +// #define GHC_USE_STD_FS +// #include +// namespace fs = std::filesystem; +// #endif +// #endif +// #ifndef GHC_USE_STD_FS +// #include +// namespace fs = ghc::filesystem; +// #endif +// +//--------------------------------------------------------------------------------------- +#ifndef GHC_FILESYSTEM_H +#define GHC_FILESYSTEM_H + +// #define BSD manifest constant only in +// sys/param.h +#ifndef _WIN32 +#include +#endif + +#ifndef GHC_OS_DETECTED +#if defined(__APPLE__) && defined(__MACH__) +#define GHC_OS_MACOS +#elif defined(__linux__) +#define GHC_OS_LINUX +#if defined(__ANDROID__) +#define GHC_OS_ANDROID +#endif +#elif defined(_WIN64) +#define GHC_OS_WINDOWS +#define GHC_OS_WIN64 +#elif defined(_WIN32) +#define GHC_OS_WINDOWS +#define GHC_OS_WIN32 +#elif defined(__CYGWIN__) +#define GHC_OS_CYGWIN +#elif defined(__sun) && defined(__SVR4) +#define GHC_OS_SOLARIS +#elif defined(__svr4__) +#define GHC_OS_SYS5R4 +#elif defined(BSD) +#define GHC_OS_BSD +#elif defined(__EMSCRIPTEN__) +#define GHC_OS_WEB +#include +#elif defined(__QNX__) +#define GHC_OS_QNX +#else +#error "Operating system currently not supported!" +#endif +#define GHC_OS_DETECTED +#if (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#if _MSVC_LANG == 201703L +#define GHC_FILESYSTEM_RUNNING_CPP17 +#else +#define GHC_FILESYSTEM_RUNNING_CPP20 +#endif +#elif (defined(__cplusplus) && __cplusplus >= 201703L) +#if __cplusplus == 201703L +#define GHC_FILESYSTEM_RUNNING_CPP17 +#else +#define GHC_FILESYSTEM_RUNNING_CPP20 +#endif +#endif +#endif + +#if defined(GHC_FILESYSTEM_IMPLEMENTATION) +#define GHC_EXPAND_IMPL +#define GHC_INLINE +#ifdef GHC_OS_WINDOWS +#ifndef GHC_FS_API +#define GHC_FS_API +#endif +#ifndef GHC_FS_API_CLASS +#define GHC_FS_API_CLASS +#endif +#else +#ifndef GHC_FS_API +#define GHC_FS_API __attribute__((visibility("default"))) +#endif +#ifndef GHC_FS_API_CLASS +#define GHC_FS_API_CLASS __attribute__((visibility("default"))) +#endif +#endif +#elif defined(GHC_FILESYSTEM_FWD) +#define GHC_INLINE +#ifdef GHC_OS_WINDOWS +#ifndef GHC_FS_API +#define GHC_FS_API extern +#endif +#ifndef GHC_FS_API_CLASS +#define GHC_FS_API_CLASS +#endif +#else +#ifndef GHC_FS_API +#define GHC_FS_API extern +#endif +#ifndef GHC_FS_API_CLASS +#define GHC_FS_API_CLASS +#endif +#endif +#else +#define GHC_EXPAND_IMPL +#define GHC_INLINE inline +#ifndef GHC_FS_API +#define GHC_FS_API +#endif +#ifndef GHC_FS_API_CLASS +#define GHC_FS_API_CLASS +#endif +#endif + +#ifdef GHC_EXPAND_IMPL + +#ifdef GHC_OS_WINDOWS +#include +// additional includes +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef GHC_OS_ANDROID +#include +#if __ANDROID_API__ < 12 +#include +#endif +#include +#define statvfs statfs +#else +#include +#endif +#ifdef GHC_OS_CYGWIN +#include +#endif +#if !defined(__ANDROID__) || __ANDROID_API__ >= 26 +#include +#endif +#endif +#ifdef GHC_OS_MACOS +#include +#endif + +#if defined(__cpp_impl_three_way_comparison) && defined(__has_include) +#if __has_include() +#define GHC_HAS_THREEWAY_COMP +#include +#endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#else // GHC_EXPAND_IMPL + +#if defined(__cpp_impl_three_way_comparison) && defined(__has_include) +#if __has_include() +#define GHC_HAS_THREEWAY_COMP +#include +#endif +#endif +#include +#include +#include +#include +#include +#include +#include +#ifdef GHC_OS_WINDOWS +#include +#endif +#endif // GHC_EXPAND_IMPL + +// After standard library includes. +// Standard library support for std::string_view. +#if defined(__cpp_lib_string_view) +#define GHC_HAS_STD_STRING_VIEW +#elif defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION >= 4000) && (__cplusplus >= 201402) +#define GHC_HAS_STD_STRING_VIEW +#elif defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE >= 7) && (__cplusplus >= 201703) +#define GHC_HAS_STD_STRING_VIEW +#elif defined(_MSC_VER) && (_MSC_VER >= 1910 && _MSVC_LANG >= 201703) +#define GHC_HAS_STD_STRING_VIEW +#endif + +// Standard library support for std::experimental::string_view. +#if defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION >= 3700 && _LIBCPP_VERSION < 7000) && (__cplusplus >= 201402) +#define GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW +#elif defined(__GNUC__) && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 9)) || (__GNUC__ > 4)) && (__cplusplus >= 201402) +#define GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW +#elif defined(__GLIBCXX__) && defined(_GLIBCXX_USE_DUAL_ABI) && (__cplusplus >= 201402) +// macro _GLIBCXX_USE_DUAL_ABI is always defined in libstdc++ from gcc-5 and newer +#define GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW +#endif + +#if defined(GHC_HAS_STD_STRING_VIEW) +#include +#elif defined(GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW) +#include +#endif + +#if !defined(GHC_OS_WINDOWS) && !defined(PATH_MAX) +#define PATH_MAX 4096 +#endif + +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Behaviour Switches (see README.md, should match the config in test/filesystem_test.cpp): +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Enforce C++17 API where possible when compiling for C++20, handles the following cases: +// * fs::path::u8string() returns std::string instead of std::u8string +// #define GHC_FILESYSTEM_ENFORCE_CPP17_API +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2682 disables the since then invalid use of the copy option create_symlinks on directories +// configure LWG conformance () +#define LWG_2682_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2395 makes crate_directory/create_directories not emit an error if there is a regular +// file with that name, it is superseded by P1164R1, so only activate if really needed +// #define LWG_2935_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2936 enables new element wise (more expensive) path comparison +// * if this->root_name().native().compare(p.root_name().native()) != 0 return result +// * if this->has_root_directory() and !p.has_root_directory() return -1 +// * if !this->has_root_directory() and p.has_root_directory() return -1 +// * else result of element wise comparison of path iteration where first comparison is != 0 or 0 +// if all comparisons are 0 (on Windows this implementation does case-insensitive root_name() +// comparison) +#define LWG_2936_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2937 enforces that fs::equivalent emits an error, if !fs::exists(p1)||!exists(p2) +#define LWG_2937_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// UTF8-Everywhere is the original behaviour of ghc::filesystem. But since v1.5 the Windows +// version defaults to std::wstring storage backend. Still all std::string will be interpreted +// as UTF-8 encoded. With this define you can enforce the old behavior on Windows, using +// std::string as backend and for fs::path::native() and char for fs::path::c_str(). This +// needs more conversions, so it is (and was before v1.5) slower, bot might help keeping source +// homogeneous in a multi-platform project. +// #define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Raise errors/exceptions when invalid unicode codepoints or UTF-8 sequences are found, +// instead of replacing them with the unicode replacement character (U+FFFD). +// #define GHC_RAISE_UNICODE_ERRORS +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Automatic prefix windows path with "\\?\" if they would break the MAX_PATH length. +// instead of replacing them with the unicode replacement character (U+FFFD). +#ifndef GHC_WIN_DISABLE_AUTO_PREFIXES +#define GHC_WIN_AUTO_PREFIX_LONG_PATH +#endif // GHC_WIN_DISABLE_AUTO_PREFIXES +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +// ghc::filesystem version in decimal (major * 10000 + minor * 100 + patch) +#define GHC_FILESYSTEM_VERSION 10512L + +// TinyUSDZ mod +#ifndef GHC_NO_EXCEPTION +#define GHC_NO_EXCEPTION +#endif + +#if defined(GHC_NO_EXCEPTION) + +// Should go here. +#if defined(GHC_RAISE_UNICODE_ERRORS) +#error "Can't raise unicode errors with exception support disabled" +#endif + +#else + +#if !defined(GHC_WITH_EXCEPTIONS) && (defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND)) +#define GHC_WITH_EXCEPTIONS +#endif +#if !defined(GHC_WITH_EXCEPTIONS) && defined(GHC_RAISE_UNICODE_ERRORS) +#error "Can't raise unicode errors with exception support disabled" +#endif + +#endif + +namespace ghc { +namespace filesystem { + +#if defined(GHC_HAS_CUSTOM_STRING_VIEW) +#define GHC_WITH_STRING_VIEW +#elif defined(GHC_HAS_STD_STRING_VIEW) +#define GHC_WITH_STRING_VIEW +using std::basic_string_view; +#elif defined(GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW) +#define GHC_WITH_STRING_VIEW +using std::experimental::basic_string_view; +#endif + +// temporary existing exception type for yet unimplemented parts +class GHC_FS_API_CLASS not_implemented_exception : public std::logic_error +{ +public: + not_implemented_exception() + : std::logic_error("function not implemented yet.") + { + } +}; + +template +class path_helper_base +{ +public: + using value_type = char_type; +#ifdef GHC_OS_WINDOWS + static constexpr value_type preferred_separator = '\\'; +#else + static constexpr value_type preferred_separator = '/'; +#endif +}; + +#if __cplusplus < 201703L +template +constexpr char_type path_helper_base::preferred_separator; +#endif + +#ifdef GHC_OS_WINDOWS +class path; +namespace detail { +bool has_executable_extension(const path& p); +} +#endif + +// [fs.class.path] class path +class GHC_FS_API_CLASS path +#if defined(GHC_OS_WINDOWS) && !defined(GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE) +#define GHC_USE_WCHAR_T +#define GHC_NATIVEWP(p) p.c_str() +#define GHC_PLATFORM_LITERAL(str) L##str + : private path_helper_base +{ +public: + using path_helper_base::value_type; +#else +#define GHC_NATIVEWP(p) p.wstring().c_str() +#define GHC_PLATFORM_LITERAL(str) str + : private path_helper_base +{ +public: + using path_helper_base::value_type; +#endif + using string_type = std::basic_string; + using path_helper_base::preferred_separator; + + // [fs.enum.path.format] enumeration format + /// The path format in which the constructor argument is given. + enum format { + generic_format, ///< The generic format, internally used by + ///< ghc::filesystem::path with slashes + native_format, ///< The format native to the current platform this code + ///< is build for + auto_format, ///< Try to auto-detect the format, fallback to native + }; + + template + struct _is_basic_string : std::false_type + { + }; + template + struct _is_basic_string> : std::true_type + { + }; + template + struct _is_basic_string, std::allocator>> : std::true_type + { + }; +#ifdef GHC_WITH_STRING_VIEW + template + struct _is_basic_string> : std::true_type + { + }; + template + struct _is_basic_string>> : std::true_type + { + }; +#endif + + template + using path_type = typename std::enable_if::value, path>::type; + template +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) + using path_from_string = + typename std::enable_if<_is_basic_string::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || + std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || + std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || + std::is_same::type>::value, + path>::type; + template + using path_type_EcharT = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value, path>::type; +#else + using path_from_string = + typename std::enable_if<_is_basic_string::value || std::is_same::type>::value || std::is_same::type>::value || + std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || + std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value, + path>::type; + template + using path_type_EcharT = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value, path>::type; +#endif + // [fs.path.construct] constructors and destructor + path() noexcept; + path(const path& p); + path(path&& p) noexcept; + path(string_type&& source, format fmt = auto_format); + template > + path(const Source& source, format fmt = auto_format); + template + path(InputIterator first, InputIterator last, format fmt = auto_format); +#ifdef GHC_WITH_EXCEPTIONS + template > + path(const Source& source, const std::locale& loc, format fmt = auto_format); + template + path(InputIterator first, InputIterator last, const std::locale& loc, format fmt = auto_format); +#endif + ~path(); + + // [fs.path.assign] assignments + path& operator=(const path& p); + path& operator=(path&& p) noexcept; + path& operator=(string_type&& source); + path& assign(string_type&& source); + template + path& operator=(const Source& source); + template + path& assign(const Source& source); + template + path& assign(InputIterator first, InputIterator last); + + // [fs.path.append] appends + path& operator/=(const path& p); + template + path& operator/=(const Source& source); + template + path& append(const Source& source); + template + path& append(InputIterator first, InputIterator last); + + // [fs.path.concat] concatenation + path& operator+=(const path& x); + path& operator+=(const string_type& x); +#ifdef GHC_WITH_STRING_VIEW + path& operator+=(basic_string_view x); +#endif + path& operator+=(const value_type* x); + path& operator+=(value_type x); + template + path_from_string& operator+=(const Source& x); + template + path_type_EcharT& operator+=(EcharT x); + template + path& concat(const Source& x); + template + path& concat(InputIterator first, InputIterator last); + + // [fs.path.modifiers] modifiers + void clear() noexcept; + path& make_preferred(); + path& remove_filename(); + path& replace_filename(const path& replacement); + path& replace_extension(const path& replacement = path()); + void swap(path& rhs) noexcept; + + // [fs.path.native.obs] native format observers + const string_type& native() const noexcept; + const value_type* c_str() const noexcept; + operator string_type() const; + template , class Allocator = std::allocator> + std::basic_string string(const Allocator& a = Allocator()) const; + std::string string() const; + std::wstring wstring() const; +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) + std::u8string u8string() const; +#else + std::string u8string() const; +#endif + std::u16string u16string() const; + std::u32string u32string() const; + + // [fs.path.generic.obs] generic format observers + template , class Allocator = std::allocator> + std::basic_string generic_string(const Allocator& a = Allocator()) const; + std::string generic_string() const; + std::wstring generic_wstring() const; +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) + std::u8string generic_u8string() const; +#else + std::string generic_u8string() const; +#endif + std::u16string generic_u16string() const; + std::u32string generic_u32string() const; + + // [fs.path.compare] compare + int compare(const path& p) const noexcept; + int compare(const string_type& s) const; +#ifdef GHC_WITH_STRING_VIEW + int compare(basic_string_view s) const; +#endif + int compare(const value_type* s) const; + + // [fs.path.decompose] decomposition + path root_name() const; + path root_directory() const; + path root_path() const; + path relative_path() const; + path parent_path() const; + path filename() const; + path stem() const; + path extension() const; + + // [fs.path.query] query + bool empty() const noexcept; + bool has_root_name() const; + bool has_root_directory() const; + bool has_root_path() const; + bool has_relative_path() const; + bool has_parent_path() const; + bool has_filename() const; + bool has_stem() const; + bool has_extension() const; + bool is_absolute() const; + bool is_relative() const; + + // [fs.path.gen] generation + path lexically_normal() const; + path lexically_relative(const path& base) const; + path lexically_proximate(const path& base) const; + + // [fs.path.itr] iterators + class iterator; + using const_iterator = iterator; + iterator begin() const; + iterator end() const; + +private: + using impl_value_type = value_type; + using impl_string_type = std::basic_string; + friend class directory_iterator; + void append_name(const value_type* name); + static constexpr impl_value_type generic_separator = '/'; + template + class input_iterator_range + { + public: + typedef InputIterator iterator; + typedef InputIterator const_iterator; + typedef typename InputIterator::difference_type difference_type; + + input_iterator_range(const InputIterator& first, const InputIterator& last) + : _first(first) + , _last(last) + { + } + + InputIterator begin() const { return _first; } + InputIterator end() const { return _last; } + + private: + InputIterator _first; + InputIterator _last; + }; + friend void swap(path& lhs, path& rhs) noexcept; + friend size_t hash_value(const path& p) noexcept; + friend path canonical(const path& p, std::error_code& ec); + friend bool create_directories(const path& p, std::error_code& ec) noexcept; + string_type::size_type root_name_length() const noexcept; + void postprocess_path_with_format(format fmt); + void check_long_path(); + impl_string_type _path; +#ifdef GHC_OS_WINDOWS + void handle_prefixes(); + friend bool detail::has_executable_extension(const path& p); +#ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH + string_type::size_type _prefixLength{0}; +#else // GHC_WIN_AUTO_PREFIX_LONG_PATH + static const string_type::size_type _prefixLength{0}; +#endif // GHC_WIN_AUTO_PREFIX_LONG_PATH +#else + static const string_type::size_type _prefixLength{0}; +#endif +}; + +// [fs.path.nonmember] path non-member functions +GHC_FS_API void swap(path& lhs, path& rhs) noexcept; +GHC_FS_API size_t hash_value(const path& p) noexcept; +#ifdef GHC_HAS_THREEWAY_COMP +GHC_FS_API std::strong_ordering operator<=>(const path& lhs, const path& rhs) noexcept; +#endif +GHC_FS_API bool operator==(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator!=(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator<(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator<=(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator>(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator>=(const path& lhs, const path& rhs) noexcept; +GHC_FS_API path operator/(const path& lhs, const path& rhs); + +// [fs.path.io] path inserter and extractor +template +std::basic_ostream& operator<<(std::basic_ostream& os, const path& p); +template +std::basic_istream& operator>>(std::basic_istream& is, path& p); + +// [pfs.path.factory] path factory functions +template > +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) +[[deprecated("use ghc::filesystem::path::path() with std::u8string instead")]] +#endif +path u8path(const Source& source); +template +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) +[[deprecated("use ghc::filesystem::path::path() with std::u8string instead")]] +#endif +path u8path(InputIterator first, InputIterator last); + +// [fs.class.filesystem_error] class filesystem_error +class GHC_FS_API_CLASS filesystem_error : public std::system_error +{ +public: + filesystem_error(const std::string& what_arg, std::error_code ec); + filesystem_error(const std::string& what_arg, const path& p1, std::error_code ec); + filesystem_error(const std::string& what_arg, const path& p1, const path& p2, std::error_code ec); + const path& path1() const noexcept; + const path& path2() const noexcept; + const char* what() const noexcept override; + +private: + std::string _what_arg; + std::error_code _ec; + path _p1, _p2; +}; + +class GHC_FS_API_CLASS path::iterator +{ +public: + using value_type = const path; + using difference_type = std::ptrdiff_t; + using pointer = const path*; + using reference = const path&; + using iterator_category = std::bidirectional_iterator_tag; + + iterator(); + iterator(const path& p, const impl_string_type::const_iterator& pos); + iterator& operator++(); + iterator operator++(int); + iterator& operator--(); + iterator operator--(int); + bool operator==(const iterator& other) const; + bool operator!=(const iterator& other) const; + reference operator*() const; + pointer operator->() const; + +private: + friend class path; + impl_string_type::const_iterator increment(const impl_string_type::const_iterator& pos) const; + impl_string_type::const_iterator decrement(const impl_string_type::const_iterator& pos) const; + void updateCurrent(); + impl_string_type::const_iterator _first; + impl_string_type::const_iterator _last; + impl_string_type::const_iterator _prefix; + impl_string_type::const_iterator _root; + impl_string_type::const_iterator _iter; + path _current; +}; + +struct space_info +{ + uintmax_t capacity; + uintmax_t free; + uintmax_t available; +}; + +// [fs.enum] enumerations +// [fs.enum.file_type] +enum class file_type { + none, + not_found, + regular, + directory, + symlink, + block, + character, + fifo, + socket, + unknown, +}; + +// [fs.enum.perms] +enum class perms : uint16_t { + none = 0, + + owner_read = 0400, + owner_write = 0200, + owner_exec = 0100, + owner_all = 0700, + + group_read = 040, + group_write = 020, + group_exec = 010, + group_all = 070, + + others_read = 04, + others_write = 02, + others_exec = 01, + others_all = 07, + + all = 0777, + set_uid = 04000, + set_gid = 02000, + sticky_bit = 01000, + + mask = 07777, + unknown = 0xffff +}; + +// [fs.enum.perm.opts] +enum class perm_options : uint16_t { + replace = 3, + add = 1, + remove = 2, + nofollow = 4, +}; + +// [fs.enum.copy.opts] +enum class copy_options : uint16_t { + none = 0, + + skip_existing = 1, + overwrite_existing = 2, + update_existing = 4, + + recursive = 8, + + copy_symlinks = 0x10, + skip_symlinks = 0x20, + + directories_only = 0x40, + create_symlinks = 0x80, +#ifndef GHC_OS_WEB + create_hard_links = 0x100 +#endif +}; + +// [fs.enum.dir.opts] +enum class directory_options : uint16_t { + none = 0, + follow_directory_symlink = 1, + skip_permission_denied = 2, +}; + +// [fs.class.file_status] class file_status +class GHC_FS_API_CLASS file_status +{ +public: + // [fs.file_status.cons] constructors and destructor + file_status() noexcept; + explicit file_status(file_type ft, perms prms = perms::unknown) noexcept; + file_status(const file_status&) noexcept; + file_status(file_status&&) noexcept; + ~file_status(); + // assignments: + file_status& operator=(const file_status&) noexcept; + file_status& operator=(file_status&&) noexcept; + // [fs.file_status.mods] modifiers + void type(file_type ft) noexcept; + void permissions(perms prms) noexcept; + // [fs.file_status.obs] observers + file_type type() const noexcept; + perms permissions() const noexcept; + friend bool operator==(const file_status& lhs, const file_status& rhs) noexcept { return lhs.type() == rhs.type() && lhs.permissions() == rhs.permissions(); } + +private: + file_type _type; + perms _perms; +}; + +using file_time_type = std::chrono::time_point; + +// [fs.class.directory_entry] Class directory_entry +class GHC_FS_API_CLASS directory_entry +{ +public: + // [fs.dir.entry.cons] constructors and destructor + directory_entry() noexcept = default; + directory_entry(const directory_entry&) = default; + directory_entry(directory_entry&&) noexcept = default; +#ifdef GHC_WITH_EXCEPTIONS + explicit directory_entry(const path& p); +#endif + directory_entry(const path& p, std::error_code& ec); + ~directory_entry(); + + // assignments: + directory_entry& operator=(const directory_entry&) = default; + directory_entry& operator=(directory_entry&&) noexcept = default; + + // [fs.dir.entry.mods] modifiers +#ifdef GHC_WITH_EXCEPTIONS + void assign(const path& p); + void replace_filename(const path& p); + void refresh(); +#endif + void assign(const path& p, std::error_code& ec); + void replace_filename(const path& p, std::error_code& ec); + void refresh(std::error_code& ec) noexcept; + + // [fs.dir.entry.obs] observers + const filesystem::path& path() const noexcept; + operator const filesystem::path&() const noexcept; +#ifdef GHC_WITH_EXCEPTIONS + bool exists() const; + bool is_block_file() const; + bool is_character_file() const; + bool is_directory() const; + bool is_fifo() const; + bool is_other() const; + bool is_regular_file() const; + bool is_socket() const; + bool is_symlink() const; + uintmax_t file_size() const; + file_time_type last_write_time() const; + file_status status() const; + file_status symlink_status() const; +#endif + bool exists(std::error_code& ec) const noexcept; + bool is_block_file(std::error_code& ec) const noexcept; + bool is_character_file(std::error_code& ec) const noexcept; + bool is_directory(std::error_code& ec) const noexcept; + bool is_fifo(std::error_code& ec) const noexcept; + bool is_other(std::error_code& ec) const noexcept; + bool is_regular_file(std::error_code& ec) const noexcept; + bool is_socket(std::error_code& ec) const noexcept; + bool is_symlink(std::error_code& ec) const noexcept; + uintmax_t file_size(std::error_code& ec) const noexcept; + file_time_type last_write_time(std::error_code& ec) const noexcept; + file_status status(std::error_code& ec) const noexcept; + file_status symlink_status(std::error_code& ec) const noexcept; + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS + uintmax_t hard_link_count() const; +#endif + uintmax_t hard_link_count(std::error_code& ec) const noexcept; +#endif + +#ifdef GHC_HAS_THREEWAY_COMP + std::strong_ordering operator<=>(const directory_entry& rhs) const noexcept; +#endif + bool operator<(const directory_entry& rhs) const noexcept; + bool operator==(const directory_entry& rhs) const noexcept; + bool operator!=(const directory_entry& rhs) const noexcept; + bool operator<=(const directory_entry& rhs) const noexcept; + bool operator>(const directory_entry& rhs) const noexcept; + bool operator>=(const directory_entry& rhs) const noexcept; + +private: + friend class directory_iterator; +#ifdef GHC_WITH_EXCEPTIONS + file_type status_file_type() const; +#endif + file_type status_file_type(std::error_code& ec) const noexcept; + filesystem::path _path; + file_status _status; + file_status _symlink_status; + uintmax_t _file_size = static_cast(-1); +#ifndef GHC_OS_WINDOWS + uintmax_t _hard_link_count = static_cast(-1); +#endif + time_t _last_write_time = 0; +}; + +// [fs.class.directory.iterator] Class directory_iterator +class GHC_FS_API_CLASS directory_iterator +{ +public: + class GHC_FS_API_CLASS proxy + { + public: + const directory_entry& operator*() const& noexcept { return _dir_entry; } + directory_entry operator*() && noexcept { return std::move(_dir_entry); } + + private: + explicit proxy(const directory_entry& dir_entry) + : _dir_entry(dir_entry) + { + } + friend class directory_iterator; + friend class recursive_directory_iterator; + directory_entry _dir_entry; + }; + using iterator_category = std::input_iterator_tag; + using value_type = directory_entry; + using difference_type = std::ptrdiff_t; + using pointer = const directory_entry*; + using reference = const directory_entry&; + + // [fs.dir.itr.members] member functions + directory_iterator() noexcept; +#ifdef GHC_WITH_EXCEPTIONS + explicit directory_iterator(const path& p); + directory_iterator(const path& p, directory_options options); +#endif + directory_iterator(const path& p, std::error_code& ec) noexcept; + directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept; + directory_iterator(const directory_iterator& rhs); + directory_iterator(directory_iterator&& rhs) noexcept; + ~directory_iterator(); + directory_iterator& operator=(const directory_iterator& rhs); + directory_iterator& operator=(directory_iterator&& rhs) noexcept; + const directory_entry& operator*() const; + const directory_entry* operator->() const; +#ifdef GHC_WITH_EXCEPTIONS + directory_iterator& operator++(); +#endif + directory_iterator& increment(std::error_code& ec) noexcept; + + // other members as required by [input.iterators] +#ifdef GHC_WITH_EXCEPTIONS + proxy operator++(int) + { + proxy p{**this}; + ++*this; + return p; + } +#endif + bool operator==(const directory_iterator& rhs) const; + bool operator!=(const directory_iterator& rhs) const; + +private: + friend class recursive_directory_iterator; + class impl; + std::shared_ptr _impl; +}; + +// [fs.dir.itr.nonmembers] directory_iterator non-member functions +GHC_FS_API directory_iterator begin(directory_iterator iter) noexcept; +GHC_FS_API directory_iterator end(const directory_iterator&) noexcept; + +// [fs.class.re.dir.itr] class recursive_directory_iterator +class GHC_FS_API_CLASS recursive_directory_iterator +{ +public: + using iterator_category = std::input_iterator_tag; + using value_type = directory_entry; + using difference_type = std::ptrdiff_t; + using pointer = const directory_entry*; + using reference = const directory_entry&; + + // [fs.rec.dir.itr.members] constructors and destructor + recursive_directory_iterator() noexcept; +#ifdef GHC_WITH_EXCEPTIONS + explicit recursive_directory_iterator(const path& p); + recursive_directory_iterator(const path& p, directory_options options); +#endif + recursive_directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept; + recursive_directory_iterator(const path& p, std::error_code& ec) noexcept; + recursive_directory_iterator(const recursive_directory_iterator& rhs); + recursive_directory_iterator(recursive_directory_iterator&& rhs) noexcept; + ~recursive_directory_iterator(); + + // [fs.rec.dir.itr.members] observers + directory_options options() const; + int depth() const; + bool recursion_pending() const; + + const directory_entry& operator*() const; + const directory_entry* operator->() const; + + // [fs.rec.dir.itr.members] modifiers recursive_directory_iterator& + recursive_directory_iterator& operator=(const recursive_directory_iterator& rhs); + recursive_directory_iterator& operator=(recursive_directory_iterator&& rhs) noexcept; +#ifdef GHC_WITH_EXCEPTIONS + recursive_directory_iterator& operator++(); +#endif + recursive_directory_iterator& increment(std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS + void pop(); +#endif + void pop(std::error_code& ec); + void disable_recursion_pending(); + + // other members as required by [input.iterators] +#ifdef GHC_WITH_EXCEPTIONS + directory_iterator::proxy operator++(int) + { + directory_iterator::proxy proxy{**this}; + ++*this; + return proxy; + } +#endif + bool operator==(const recursive_directory_iterator& rhs) const; + bool operator!=(const recursive_directory_iterator& rhs) const; + +private: + struct recursive_directory_iterator_impl + { + directory_options _options; + bool _recursion_pending; + std::stack _dir_iter_stack; + recursive_directory_iterator_impl(directory_options options, bool recursion_pending) + : _options(options) + , _recursion_pending(recursion_pending) + { + } + }; + std::shared_ptr _impl; +}; + +// [fs.rec.dir.itr.nonmembers] directory_iterator non-member functions +GHC_FS_API recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept; +GHC_FS_API recursive_directory_iterator end(const recursive_directory_iterator&) noexcept; + +// [fs.op.funcs] filesystem operations +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API path absolute(const path& p); +GHC_FS_API path canonical(const path& p); +GHC_FS_API void copy(const path& from, const path& to); +GHC_FS_API void copy(const path& from, const path& to, copy_options options); +GHC_FS_API bool copy_file(const path& from, const path& to); +GHC_FS_API bool copy_file(const path& from, const path& to, copy_options option); +GHC_FS_API void copy_symlink(const path& existing_symlink, const path& new_symlink); +GHC_FS_API bool create_directories(const path& p); +GHC_FS_API bool create_directory(const path& p); +GHC_FS_API bool create_directory(const path& p, const path& attributes); +GHC_FS_API void create_directory_symlink(const path& to, const path& new_symlink); +GHC_FS_API void create_symlink(const path& to, const path& new_symlink); +GHC_FS_API path current_path(); +GHC_FS_API void current_path(const path& p); +GHC_FS_API bool exists(const path& p); +GHC_FS_API bool equivalent(const path& p1, const path& p2); +GHC_FS_API uintmax_t file_size(const path& p); +GHC_FS_API bool is_block_file(const path& p); +GHC_FS_API bool is_character_file(const path& p); +GHC_FS_API bool is_directory(const path& p); +GHC_FS_API bool is_empty(const path& p); +GHC_FS_API bool is_fifo(const path& p); +GHC_FS_API bool is_other(const path& p); +GHC_FS_API bool is_regular_file(const path& p); +GHC_FS_API bool is_socket(const path& p); +GHC_FS_API bool is_symlink(const path& p); +GHC_FS_API file_time_type last_write_time(const path& p); +GHC_FS_API void last_write_time(const path& p, file_time_type new_time); +GHC_FS_API void permissions(const path& p, perms prms, perm_options opts = perm_options::replace); +GHC_FS_API path proximate(const path& p, const path& base = current_path()); +GHC_FS_API path read_symlink(const path& p); +GHC_FS_API path relative(const path& p, const path& base = current_path()); +GHC_FS_API bool remove(const path& p); +GHC_FS_API uintmax_t remove_all(const path& p); +GHC_FS_API void rename(const path& from, const path& to); +GHC_FS_API void resize_file(const path& p, uintmax_t size); +GHC_FS_API space_info space(const path& p); +GHC_FS_API file_status status(const path& p); +GHC_FS_API file_status symlink_status(const path& p); +GHC_FS_API path temp_directory_path(); +GHC_FS_API path weakly_canonical(const path& p); +#endif +GHC_FS_API path absolute(const path& p, std::error_code& ec); +GHC_FS_API path canonical(const path& p, std::error_code& ec); +GHC_FS_API void copy(const path& from, const path& to, std::error_code& ec) noexcept; +GHC_FS_API void copy(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept; +GHC_FS_API bool copy_file(const path& from, const path& to, std::error_code& ec) noexcept; +GHC_FS_API bool copy_file(const path& from, const path& to, copy_options option, std::error_code& ec) noexcept; +GHC_FS_API void copy_symlink(const path& existing_symlink, const path& new_symlink, std::error_code& ec) noexcept; +GHC_FS_API bool create_directories(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool create_directory(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool create_directory(const path& p, const path& attributes, std::error_code& ec) noexcept; +GHC_FS_API void create_directory_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept; +GHC_FS_API void create_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept; +GHC_FS_API path current_path(std::error_code& ec); +GHC_FS_API void current_path(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool exists(file_status s) noexcept; +GHC_FS_API bool exists(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool equivalent(const path& p1, const path& p2, std::error_code& ec) noexcept; +GHC_FS_API uintmax_t file_size(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_block_file(file_status s) noexcept; +GHC_FS_API bool is_block_file(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_character_file(file_status s) noexcept; +GHC_FS_API bool is_character_file(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_directory(file_status s) noexcept; +GHC_FS_API bool is_directory(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_empty(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_fifo(file_status s) noexcept; +GHC_FS_API bool is_fifo(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_other(file_status s) noexcept; +GHC_FS_API bool is_other(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_regular_file(file_status s) noexcept; +GHC_FS_API bool is_regular_file(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_socket(file_status s) noexcept; +GHC_FS_API bool is_socket(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_symlink(file_status s) noexcept; +GHC_FS_API bool is_symlink(const path& p, std::error_code& ec) noexcept; +GHC_FS_API file_time_type last_write_time(const path& p, std::error_code& ec) noexcept; +GHC_FS_API void last_write_time(const path& p, file_time_type new_time, std::error_code& ec) noexcept; +GHC_FS_API void permissions(const path& p, perms prms, std::error_code& ec) noexcept; +GHC_FS_API void permissions(const path& p, perms prms, perm_options opts, std::error_code& ec) noexcept; +GHC_FS_API path proximate(const path& p, std::error_code& ec); +GHC_FS_API path proximate(const path& p, const path& base, std::error_code& ec); +GHC_FS_API path read_symlink(const path& p, std::error_code& ec); +GHC_FS_API path relative(const path& p, std::error_code& ec); +GHC_FS_API path relative(const path& p, const path& base, std::error_code& ec); +GHC_FS_API bool remove(const path& p, std::error_code& ec) noexcept; +GHC_FS_API uintmax_t remove_all(const path& p, std::error_code& ec) noexcept; +GHC_FS_API void rename(const path& from, const path& to, std::error_code& ec) noexcept; +GHC_FS_API void resize_file(const path& p, uintmax_t size, std::error_code& ec) noexcept; +GHC_FS_API space_info space(const path& p, std::error_code& ec) noexcept; +GHC_FS_API file_status status(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool status_known(file_status s) noexcept; +GHC_FS_API file_status symlink_status(const path& p, std::error_code& ec) noexcept; +GHC_FS_API path temp_directory_path(std::error_code& ec) noexcept; +GHC_FS_API path weakly_canonical(const path& p, std::error_code& ec) noexcept; + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API void create_hard_link(const path& to, const path& new_hard_link); +GHC_FS_API uintmax_t hard_link_count(const path& p); +#endif +GHC_FS_API void create_hard_link(const path& to, const path& new_hard_link, std::error_code& ec) noexcept; +GHC_FS_API uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcept; +#endif + +// Non-C++17 add-on std::fstream wrappers with path +template > +class basic_filebuf : public std::basic_filebuf +{ +public: + basic_filebuf() {} + ~basic_filebuf() override {} + basic_filebuf(const basic_filebuf&) = delete; + const basic_filebuf& operator=(const basic_filebuf&) = delete; + basic_filebuf* open(const path& p, std::ios_base::openmode mode) + { +#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) + return std::basic_filebuf::open(p.wstring().c_str(), mode) ? this : 0; +#else + return std::basic_filebuf::open(p.string().c_str(), mode) ? this : 0; +#endif + } +}; + +template > +class basic_ifstream : public std::basic_ifstream +{ +public: + basic_ifstream() {} +#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) + explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in) + : std::basic_ifstream(p.wstring().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in) { std::basic_ifstream::open(p.wstring().c_str(), mode); } +#else + explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in) + : std::basic_ifstream(p.string().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in) { std::basic_ifstream::open(p.string().c_str(), mode); } +#endif + basic_ifstream(const basic_ifstream&) = delete; + const basic_ifstream& operator=(const basic_ifstream&) = delete; + ~basic_ifstream() override {} +}; + +template > +class basic_ofstream : public std::basic_ofstream +{ +public: + basic_ofstream() {} +#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) + explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out) + : std::basic_ofstream(p.wstring().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::out) { std::basic_ofstream::open(p.wstring().c_str(), mode); } +#else + explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out) + : std::basic_ofstream(p.string().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::out) { std::basic_ofstream::open(p.string().c_str(), mode); } +#endif + basic_ofstream(const basic_ofstream&) = delete; + const basic_ofstream& operator=(const basic_ofstream&) = delete; + ~basic_ofstream() override {} +}; + +template > +class basic_fstream : public std::basic_fstream +{ +public: + basic_fstream() {} +#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) + explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) + : std::basic_fstream(p.wstring().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { std::basic_fstream::open(p.wstring().c_str(), mode); } +#else + explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) + : std::basic_fstream(p.string().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { std::basic_fstream::open(p.string().c_str(), mode); } +#endif + basic_fstream(const basic_fstream&) = delete; + const basic_fstream& operator=(const basic_fstream&) = delete; + ~basic_fstream() override {} +}; + +typedef basic_filebuf filebuf; +typedef basic_filebuf wfilebuf; +typedef basic_ifstream ifstream; +typedef basic_ifstream wifstream; +typedef basic_ofstream ofstream; +typedef basic_ofstream wofstream; +typedef basic_fstream fstream; +typedef basic_fstream wfstream; + +class GHC_FS_API_CLASS u8arguments +{ +public: + u8arguments(int& argc, char**& argv); + ~u8arguments() + { + _refargc = _argc; + _refargv = _argv; + } + + bool valid() const { return _isvalid; } + +private: + int _argc; + char** _argv; + int& _refargc; + char**& _refargv; + bool _isvalid; +#ifdef GHC_OS_WINDOWS + std::vector _args; + std::vector _argp; +#endif +}; + +//------------------------------------------------------------------------------------------------- +// Implementation +//------------------------------------------------------------------------------------------------- + +namespace detail { +enum utf8_states_t { S_STRT = 0, S_RJCT = 8 }; +GHC_FS_API void appendUTF8(std::string& str, uint32_t unicode); +GHC_FS_API bool is_surrogate(uint32_t c); +GHC_FS_API bool is_high_surrogate(uint32_t c); +GHC_FS_API bool is_low_surrogate(uint32_t c); +GHC_FS_API unsigned consumeUtf8Fragment(const unsigned state, const uint8_t fragment, uint32_t& codepoint); +enum class portable_error { + none = 0, + exists, + not_found, + not_supported, + not_implemented, + invalid_argument, + is_a_directory, +}; +GHC_FS_API std::error_code make_error_code(portable_error err); +#ifdef GHC_OS_WINDOWS +GHC_FS_API std::error_code make_system_error(uint32_t err = 0); +#else +GHC_FS_API std::error_code make_system_error(int err = 0); + +template +struct has_d_type : std::false_type{}; + +template +struct has_d_type : std::true_type {}; + +template +GHC_INLINE file_type file_type_from_dirent_impl(const T&, std::false_type) +{ + return file_type::none; +} + +template +GHC_INLINE file_type file_type_from_dirent_impl(const T& t, std::true_type) +{ + switch (t.d_type) { +#ifdef DT_BLK + case DT_BLK: + return file_type::block; +#endif +#ifdef DT_CHR + case DT_CHR: + return file_type::character; +#endif +#ifdef DT_DIR + case DT_DIR: + return file_type::directory; +#endif +#ifdef DT_FIFO + case DT_FIFO: + return file_type::fifo; +#endif +#ifdef DT_LNK + case DT_LNK: + return file_type::symlink; +#endif +#ifdef DT_REG + case DT_REG: + return file_type::regular; +#endif +#ifdef DT_SOCK + case DT_SOCK: + return file_type::socket; +#endif +#ifdef DT_UNKNOWN + case DT_UNKNOWN: + return file_type::none; +#endif + default: + return file_type::unknown; + } +} + +template +GHC_INLINE file_type file_type_from_dirent(const T& t) +{ + return file_type_from_dirent_impl(t, has_d_type{}); +} +#endif +} // namespace detail + +namespace detail { + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE std::error_code make_error_code(portable_error err) +{ +#ifdef GHC_OS_WINDOWS + switch (err) { + case portable_error::none: + return std::error_code(); + case portable_error::exists: + return std::error_code(ERROR_ALREADY_EXISTS, std::system_category()); + case portable_error::not_found: + return std::error_code(ERROR_PATH_NOT_FOUND, std::system_category()); + case portable_error::not_supported: + return std::error_code(ERROR_NOT_SUPPORTED, std::system_category()); + case portable_error::not_implemented: + return std::error_code(ERROR_CALL_NOT_IMPLEMENTED, std::system_category()); + case portable_error::invalid_argument: + return std::error_code(ERROR_INVALID_PARAMETER, std::system_category()); + case portable_error::is_a_directory: +#ifdef ERROR_DIRECTORY_NOT_SUPPORTED + return std::error_code(ERROR_DIRECTORY_NOT_SUPPORTED, std::system_category()); +#else + return std::error_code(ERROR_NOT_SUPPORTED, std::system_category()); +#endif + } +#else + switch (err) { + case portable_error::none: + return std::error_code(); + case portable_error::exists: + return std::error_code(EEXIST, std::system_category()); + case portable_error::not_found: + return std::error_code(ENOENT, std::system_category()); + case portable_error::not_supported: + return std::error_code(ENOTSUP, std::system_category()); + case portable_error::not_implemented: + return std::error_code(ENOSYS, std::system_category()); + case portable_error::invalid_argument: + return std::error_code(EINVAL, std::system_category()); + case portable_error::is_a_directory: + return std::error_code(EISDIR, std::system_category()); + } +#endif + return std::error_code(); +} + +#ifdef GHC_OS_WINDOWS +GHC_INLINE std::error_code make_system_error(uint32_t err) +{ + return std::error_code(err ? static_cast(err) : static_cast(::GetLastError()), std::system_category()); +} +#else +GHC_INLINE std::error_code make_system_error(int err) +{ + return std::error_code(err ? err : errno, std::system_category()); +} +#endif + +#endif // GHC_EXPAND_IMPL + +template +using EnableBitmask = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value, Enum>::type; +} // namespace detail + +template +constexpr detail::EnableBitmask operator&(Enum X, Enum Y) +{ + using underlying = typename std::underlying_type::type; + return static_cast(static_cast(X) & static_cast(Y)); +} + +template +constexpr detail::EnableBitmask operator|(Enum X, Enum Y) +{ + using underlying = typename std::underlying_type::type; + return static_cast(static_cast(X) | static_cast(Y)); +} + +template +constexpr detail::EnableBitmask operator^(Enum X, Enum Y) +{ + using underlying = typename std::underlying_type::type; + return static_cast(static_cast(X) ^ static_cast(Y)); +} + +template +constexpr detail::EnableBitmask operator~(Enum X) +{ + using underlying = typename std::underlying_type::type; + return static_cast(~static_cast(X)); +} + +template +detail::EnableBitmask& operator&=(Enum& X, Enum Y) +{ + X = X & Y; + return X; +} + +template +detail::EnableBitmask& operator|=(Enum& X, Enum Y) +{ + X = X | Y; + return X; +} + +template +detail::EnableBitmask& operator^=(Enum& X, Enum Y) +{ + X = X ^ Y; + return X; +} + +#ifdef GHC_EXPAND_IMPL + +namespace detail { + +GHC_INLINE bool in_range(uint32_t c, uint32_t lo, uint32_t hi) +{ + return (static_cast(c - lo) < (hi - lo + 1)); +} + +GHC_INLINE bool is_surrogate(uint32_t c) +{ + return in_range(c, 0xd800, 0xdfff); +} + +GHC_INLINE bool is_high_surrogate(uint32_t c) +{ + return (c & 0xfffffc00) == 0xd800; +} + +GHC_INLINE bool is_low_surrogate(uint32_t c) +{ + return (c & 0xfffffc00) == 0xdc00; +} + +GHC_INLINE void appendUTF8(std::string& str, uint32_t unicode) +{ + if (unicode <= 0x7f) { + str.push_back(static_cast(unicode)); + } + else if (unicode >= 0x80 && unicode <= 0x7ff) { + str.push_back(static_cast((unicode >> 6) + 192)); + str.push_back(static_cast((unicode & 0x3f) + 128)); + } + else if ((unicode >= 0x800 && unicode <= 0xd7ff) || (unicode >= 0xe000 && unicode <= 0xffff)) { + str.push_back(static_cast((unicode >> 12) + 224)); + str.push_back(static_cast(((unicode & 0xfff) >> 6) + 128)); + str.push_back(static_cast((unicode & 0x3f) + 128)); + } + else if (unicode >= 0x10000 && unicode <= 0x10ffff) { + str.push_back(static_cast((unicode >> 18) + 240)); + str.push_back(static_cast(((unicode & 0x3ffff) >> 12) + 128)); + str.push_back(static_cast(((unicode & 0xfff) >> 6) + 128)); + str.push_back(static_cast((unicode & 0x3f) + 128)); + } + else { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal code point for unicode character.", str, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + appendUTF8(str, 0xfffd); +#endif + } +} + +// Thanks to Bjoern Hoehrmann (https://bjoern.hoehrmann.de/utf-8/decoder/dfa/) +// and Taylor R Campbell for the ideas to this DFA approach of UTF-8 decoding; +// Generating debugging and shrinking my own DFA from scratch was a day of fun! +GHC_INLINE unsigned consumeUtf8Fragment(const unsigned state, const uint8_t fragment, uint32_t& codepoint) +{ + static const uint32_t utf8_state_info[] = { + // encoded states + 0x11111111u, 0x11111111u, 0x77777777u, 0x77777777u, 0x88888888u, 0x88888888u, 0x88888888u, 0x88888888u, 0x22222299u, 0x22222222u, 0x22222222u, 0x22222222u, 0x3333333au, 0x33433333u, 0x9995666bu, 0x99999999u, + 0x88888880u, 0x22818108u, 0x88888881u, 0x88888882u, 0x88888884u, 0x88888887u, 0x88888886u, 0x82218108u, 0x82281108u, 0x88888888u, 0x88888883u, 0x88888885u, 0u, 0u, 0u, 0u, + }; + uint8_t category = fragment < 128 ? 0 : (utf8_state_info[(fragment >> 3) & 0xf] >> ((fragment & 7) << 2)) & 0xf; + codepoint = (state ? (codepoint << 6) | (fragment & 0x3fu) : (0xffu >> category) & fragment); + return state == S_RJCT ? static_cast(S_RJCT) : static_cast((utf8_state_info[category + 16] >> (state << 2)) & 0xf); +} + +GHC_INLINE bool validUtf8(const std::string& utf8String) +{ + std::string::const_iterator iter = utf8String.begin(); + unsigned utf8_state = S_STRT; + std::uint32_t codepoint = 0; + while (iter < utf8String.end()) { + if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_RJCT) { + return false; + } + } + if (utf8_state) { + return false; + } + return true; +} + +} // namespace detail + +#endif + +namespace detail { + +template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 1)>::type* = nullptr> +inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + return StringType(utf8String.begin(), utf8String.end(), alloc); +} + +template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 2)>::type* = nullptr> +inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + StringType result(alloc); + result.reserve(utf8String.length()); + auto iter = utf8String.cbegin(); + unsigned utf8_state = S_STRT; + std::uint32_t codepoint = 0; + while (iter < utf8String.cend()) { + if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_STRT) { + if (codepoint <= 0xffff) { + result += static_cast(codepoint); + } + else { + codepoint -= 0x10000; + result += static_cast((codepoint >> 10) + 0xd800); + result += static_cast((codepoint & 0x3ff) + 0xdc00); + } + codepoint = 0; + } + else if (utf8_state == S_RJCT) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); + utf8_state = S_STRT; + codepoint = 0; +#endif + } + } + if (utf8_state) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); +#endif + } + return result; +} + +template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 4)>::type* = nullptr> +inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + StringType result(alloc); + result.reserve(utf8String.length()); + auto iter = utf8String.cbegin(); + unsigned utf8_state = S_STRT; + std::uint32_t codepoint = 0; + while (iter < utf8String.cend()) { + if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_STRT) { + result += static_cast(codepoint); + codepoint = 0; + } + else if (utf8_state == S_RJCT) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); + utf8_state = S_STRT; + codepoint = 0; +#endif + } + } + if (utf8_state) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); +#endif + } + return result; +} + +template +inline StringType fromUtf8(const charT (&utf8String)[N]) +{ +#ifdef GHC_WITH_STRING_VIEW + return fromUtf8(basic_string_view(utf8String, N - 1)); +#else + return fromUtf8(std::basic_string(utf8String, N - 1)); +#endif +} + +template ::value && (sizeof(typename strT::value_type) == 1), int>::type size = 1> +inline std::string toUtf8(const strT& unicodeString) +{ + return std::string(unicodeString.begin(), unicodeString.end()); +} + +template ::value && (sizeof(typename strT::value_type) == 2), int>::type size = 2> +inline std::string toUtf8(const strT& unicodeString) +{ + std::string result; + for (auto iter = unicodeString.begin(); iter != unicodeString.end(); ++iter) { + char32_t c = *iter; + if (is_surrogate(c)) { + ++iter; + if (iter != unicodeString.end() && is_high_surrogate(c) && is_low_surrogate(*iter)) { + appendUTF8(result, (char32_t(c) << 10) + *iter - 0x35fdc00); + } + else { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal code point for unicode character.", result, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + appendUTF8(result, 0xfffd); + if (iter == unicodeString.end()) { + break; + } +#endif + } + } + else { + appendUTF8(result, c); + } + } + return result; +} + +template ::value && (sizeof(typename strT::value_type) == 4), int>::type size = 4> +inline std::string toUtf8(const strT& unicodeString) +{ + std::string result; + for (auto c : unicodeString) { + appendUTF8(result, static_cast(c)); + } + return result; +} + +template +inline std::string toUtf8(const charT* unicodeString) +{ +#ifdef GHC_WITH_STRING_VIEW + return toUtf8(basic_string_view>(unicodeString)); +#else + return toUtf8(std::basic_string>(unicodeString)); +#endif +} + +#ifdef GHC_USE_WCHAR_T +template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 1), bool>::type = false> +inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + auto temp = toUtf8(wString); + return StringType(temp.begin(), temp.end(), alloc); +} + +template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 2), bool>::type = false> +inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + return StringType(wString.begin(), wString.end(), alloc); +} + +template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 4), bool>::type = false> +inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + auto temp = toUtf8(wString); + return fromUtf8(temp, alloc); +} + +template ::value && (sizeof(typename strT::value_type) == 1), bool>::type = false> +inline std::wstring toWChar(const strT& unicodeString) +{ + return fromUtf8(unicodeString); +} + +template ::value && (sizeof(typename strT::value_type) == 2), bool>::type = false> +inline std::wstring toWChar(const strT& unicodeString) +{ + return std::wstring(unicodeString.begin(), unicodeString.end()); +} + +template ::value && (sizeof(typename strT::value_type) == 4), bool>::type = false> +inline std::wstring toWChar(const strT& unicodeString) +{ + auto temp = toUtf8(unicodeString); + return fromUtf8(temp); +} + +template +inline std::wstring toWChar(const charT* unicodeString) +{ +#ifdef GHC_WITH_STRING_VIEW + return toWChar(basic_string_view>(unicodeString)); +#else + return toWChar(std::basic_string>(unicodeString)); +#endif +} +#endif // GHC_USE_WCHAR_T + +} // namespace detail + +#ifdef GHC_EXPAND_IMPL + +namespace detail { + +template ::value, bool>::type = true> +GHC_INLINE bool startsWith(const strT& what, const strT& with) +{ + return with.length() <= what.length() && equal(with.begin(), with.end(), what.begin()); +} + +template ::value, bool>::type = true> +GHC_INLINE bool endsWith(const strT& what, const strT& with) +{ + return with.length() <= what.length() && what.compare(what.length() - with.length(), with.size(), with) == 0; +} + +} // namespace detail + +GHC_INLINE void path::check_long_path() +{ +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + if (is_absolute() && _path.length() >= MAX_PATH - 12 && !detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\")))) { + postprocess_path_with_format(native_format); + } +#endif +} + +GHC_INLINE void path::postprocess_path_with_format(path::format fmt) +{ +#ifdef GHC_RAISE_UNICODE_ERRORS + if (!detail::validUtf8(_path)) { + path t; + t._path = _path; + throw filesystem_error("Illegal byte sequence for unicode character.", t, std::make_error_code(std::errc::illegal_byte_sequence)); + } +#endif + switch (fmt) { +#ifdef GHC_OS_WINDOWS + case path::native_format: + case path::auto_format: + case path::generic_format: + for (auto& c : _path) { + if (c == generic_separator) { + c = preferred_separator; + } + } +#ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH + if (is_absolute() && _path.length() >= MAX_PATH - 12 && !detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\")))) { + _path = GHC_PLATFORM_LITERAL("\\\\?\\") + _path; + } +#endif + handle_prefixes(); + break; +#else + case path::auto_format: + case path::native_format: + case path::generic_format: + // nothing to do + break; +#endif + } + if (_path.length() > _prefixLength + 2 && _path[_prefixLength] == preferred_separator && _path[_prefixLength + 1] == preferred_separator && _path[_prefixLength + 2] != preferred_separator) { + impl_string_type::iterator new_end = std::unique(_path.begin() + static_cast(_prefixLength) + 2, _path.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == preferred_separator; }); + _path.erase(new_end, _path.end()); + } + else { + impl_string_type::iterator new_end = std::unique(_path.begin() + static_cast(_prefixLength), _path.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == preferred_separator; }); + _path.erase(new_end, _path.end()); + } +} + +#endif // GHC_EXPAND_IMPL + +template +inline path::path(const Source& source, format fmt) +#ifdef GHC_USE_WCHAR_T + : _path(detail::toWChar(source)) +#else + : _path(detail::toUtf8(source)) +#endif +{ + postprocess_path_with_format(fmt); +} + +template +inline path u8path(const Source& source) +{ + return path(source); +} +template +inline path u8path(InputIterator first, InputIterator last) +{ + return path(first, last); +} + +template +inline path::path(InputIterator first, InputIterator last, format fmt) + : path(std::basic_string::value_type>(first, last), fmt) +{ + // delegated +} + +#ifdef GHC_EXPAND_IMPL + +namespace detail { + +GHC_INLINE bool equals_simple_insensitive(const path::value_type* str1, const path::value_type* str2) +{ +#ifdef GHC_OS_WINDOWS +#ifdef __GNUC__ + while (::tolower((unsigned char)*str1) == ::tolower((unsigned char)*str2++)) { + if (*str1++ == 0) + return true; + } + return false; +#else // __GNUC__ +#ifdef GHC_USE_WCHAR_T + return 0 == ::_wcsicmp(str1, str2); +#else // GHC_USE_WCHAR_T + return 0 == ::_stricmp(str1, str2); +#endif // GHC_USE_WCHAR_T +#endif // __GNUC__ +#else // GHC_OS_WINDOWS + return 0 == ::strcasecmp(str1, str2); +#endif // GHC_OS_WINDOWS +} + +GHC_INLINE int compare_simple_insensitive(const path::value_type* str1, size_t len1, const path::value_type* str2, size_t len2) +{ + while (len1 > 0 && len2 > 0 && ::tolower(static_cast(*str1)) == ::tolower(static_cast(*str2))) { + --len1; + --len2; + ++str1; + ++str2; + } + if (len1 && len2) { + return *str1 < *str2 ? -1 : 1; + } + if (len1 == 0 && len2 == 0) { + return 0; + } + return len1 == 0 ? -1 : 1; +} + +GHC_INLINE const char* strerror_adapter(char* gnu, char*) +{ + return gnu; +} + +GHC_INLINE const char* strerror_adapter(int posix, char* buffer) +{ + if (posix) { + return "Error in strerror_r!"; + } + return buffer; +} + +template +GHC_INLINE std::string systemErrorText(ErrorNumber code = 0) +{ +#if defined(GHC_OS_WINDOWS) + LPVOID msgBuf; + DWORD dw = code ? static_cast(code) : ::GetLastError(); + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&msgBuf, 0, NULL); + std::string msg = toUtf8(std::wstring((LPWSTR)msgBuf)); + LocalFree(msgBuf); + return msg; +#else + char buffer[512]; + return strerror_adapter(strerror_r(code ? code : errno, buffer, sizeof(buffer)), buffer); +#endif +} + +#ifdef GHC_OS_WINDOWS +using CreateSymbolicLinkW_fp = BOOLEAN(WINAPI*)(LPCWSTR, LPCWSTR, DWORD); +using CreateHardLinkW_fp = BOOLEAN(WINAPI*)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES); + +GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool to_directory, std::error_code& ec) +{ + std::error_code tec; + auto fs = status(target_name, tec); + if ((fs.type() == file_type::directory && !to_directory) || (fs.type() == file_type::regular && to_directory)) { + ec = detail::make_error_code(detail::portable_error::not_supported); + return; + } +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + static CreateSymbolicLinkW_fp api_call = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateSymbolicLinkW")); +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic pop +#endif + if (api_call) { + if (api_call(GHC_NATIVEWP(new_symlink), GHC_NATIVEWP(target_name), to_directory ? 1 : 0) == 0) { + auto result = ::GetLastError(); + if (result == ERROR_PRIVILEGE_NOT_HELD && api_call(GHC_NATIVEWP(new_symlink), GHC_NATIVEWP(target_name), to_directory ? 3 : 2) != 0) { + return; + } + ec = detail::make_system_error(result); + } + } + else { + ec = detail::make_system_error(ERROR_NOT_SUPPORTED); + } +} + +GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlink, std::error_code& ec) +{ +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + static CreateHardLinkW_fp api_call = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateHardLinkW")); +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic pop +#endif + if (api_call) { + if (api_call(GHC_NATIVEWP(new_hardlink), GHC_NATIVEWP(target_name), NULL) == 0) { + ec = detail::make_system_error(); + } + } + else { + ec = detail::make_system_error(ERROR_NOT_SUPPORTED); + } +} + +GHC_INLINE path getFullPathName(const wchar_t* p, std::error_code& ec) +{ + ULONG size = ::GetFullPathNameW(p, 0, 0, 0); + if (size) { + std::vector buf(size, 0); + ULONG s2 = GetFullPathNameW(p, size, buf.data(), nullptr); + if (s2 && s2 < size) { + return path(std::wstring(buf.data(), s2)); + } + } + ec = detail::make_system_error(); + return path(); +} + +#else +GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool, std::error_code& ec) +{ + if (::symlink(target_name.c_str(), new_symlink.c_str()) != 0) { + ec = detail::make_system_error(); + } +} + +#ifndef GHC_OS_WEB +GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlink, std::error_code& ec) +{ + if (::link(target_name.c_str(), new_hardlink.c_str()) != 0) { + ec = detail::make_system_error(); + } +} +#endif +#endif + +template +GHC_INLINE file_status file_status_from_st_mode(T mode) +{ +#ifdef GHC_OS_WINDOWS + file_type ft = file_type::unknown; + if ((mode & _S_IFDIR) == _S_IFDIR) { + ft = file_type::directory; + } + else if ((mode & _S_IFREG) == _S_IFREG) { + ft = file_type::regular; + } + else if ((mode & _S_IFCHR) == _S_IFCHR) { + ft = file_type::character; + } + perms prms = static_cast(mode & 0xfff); + return file_status(ft, prms); +#else + file_type ft = file_type::unknown; + if (S_ISDIR(mode)) { + ft = file_type::directory; + } + else if (S_ISREG(mode)) { + ft = file_type::regular; + } + else if (S_ISCHR(mode)) { + ft = file_type::character; + } + else if (S_ISBLK(mode)) { + ft = file_type::block; + } + else if (S_ISFIFO(mode)) { + ft = file_type::fifo; + } + else if (S_ISLNK(mode)) { + ft = file_type::symlink; + } + else if (S_ISSOCK(mode)) { + ft = file_type::socket; + } + perms prms = static_cast(mode & 0xfff); + return file_status(ft, prms); +#endif +} + +#ifdef GHC_OS_WINDOWS + +class unique_handle +{ +public: + typedef HANDLE element_type; + + unique_handle() noexcept + : _handle(INVALID_HANDLE_VALUE) + { + } + explicit unique_handle(element_type h) noexcept + : _handle(h) + { + } + unique_handle(unique_handle&& u) noexcept + : _handle(u.release()) + { + } + ~unique_handle() { reset(); } + unique_handle& operator=(unique_handle&& u) noexcept + { + reset(u.release()); + return *this; + } + element_type get() const noexcept { return _handle; } + explicit operator bool() const noexcept { return _handle != INVALID_HANDLE_VALUE; } + element_type release() noexcept + { + element_type tmp = _handle; + _handle = INVALID_HANDLE_VALUE; + return tmp; + } + void reset(element_type h = INVALID_HANDLE_VALUE) noexcept + { + element_type tmp = _handle; + _handle = h; + if (tmp != INVALID_HANDLE_VALUE) { + CloseHandle(tmp); + } + } + void swap(unique_handle& u) noexcept { std::swap(_handle, u._handle); } + +private: + element_type _handle; +}; + +#ifndef REPARSE_DATA_BUFFER_HEADER_SIZE +typedef struct _REPARSE_DATA_BUFFER +{ + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union + { + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct + { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + } DUMMYUNIONNAME; +} REPARSE_DATA_BUFFER; +#ifndef MAXIMUM_REPARSE_DATA_BUFFER_SIZE +#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE (16 * 1024) +#endif +#endif + +template +struct free_deleter +{ + void operator()(T* p) const { std::free(p); } +}; + +GHC_INLINE std::unique_ptr> getReparseData(const path& p, std::error_code& ec) +{ + unique_handle file(CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0)); + if (!file) { + ec = detail::make_system_error(); + return nullptr; + } + + std::unique_ptr> reparseData(reinterpret_cast(std::calloc(1, MAXIMUM_REPARSE_DATA_BUFFER_SIZE))); + ULONG bufferUsed; + if (DeviceIoControl(file.get(), FSCTL_GET_REPARSE_POINT, 0, 0, reparseData.get(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bufferUsed, 0)) { + return reparseData; + } + else { + ec = detail::make_system_error(); + } + return nullptr; +} +#endif + +GHC_INLINE path resolveSymlink(const path& p, std::error_code& ec) +{ +#ifdef GHC_OS_WINDOWS + path result; + auto reparseData = detail::getReparseData(p, ec); + if (!ec) { + if (reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag)) { + switch (reparseData->ReparseTag) { + case IO_REPARSE_TAG_SYMLINK: { + auto printName = std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.PrintNameLength / sizeof(WCHAR)); + auto substituteName = + std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); + if (detail::endsWith(substituteName, printName) && detail::startsWith(substituteName, std::wstring(L"\\??\\"))) { + result = printName; + } + else { + result = substituteName; + } + if (reparseData->SymbolicLinkReparseBuffer.Flags & 0x1 /*SYMLINK_FLAG_RELATIVE*/) { + result = p.parent_path() / result; + } + break; + } + case IO_REPARSE_TAG_MOUNT_POINT: + result = detail::getFullPathName(GHC_NATIVEWP(p), ec); + // result = std::wstring(&reparseData->MountPointReparseBuffer.PathBuffer[reparseData->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); + break; + default: + break; + } + } + } + return result; +#else + size_t bufferSize = 256; + while (true) { + std::vector buffer(bufferSize, static_cast(0)); + auto rc = ::readlink(p.c_str(), buffer.data(), buffer.size()); + if (rc < 0) { + ec = detail::make_system_error(); + return path(); + } + else if (rc < static_cast(bufferSize)) { + return path(std::string(buffer.data(), static_cast(rc))); + } + bufferSize *= 2; + } + return path(); +#endif +} + +#ifdef GHC_OS_WINDOWS +GHC_INLINE time_t timeFromFILETIME(const FILETIME& ft) +{ + ULARGE_INTEGER ull; + ull.LowPart = ft.dwLowDateTime; + ull.HighPart = ft.dwHighDateTime; + return static_cast(ull.QuadPart / 10000000ULL - 11644473600ULL); +} + +GHC_INLINE void timeToFILETIME(time_t t, FILETIME& ft) +{ + LONGLONG ll; + ll = Int32x32To64(t, 10000000) + 116444736000000000; + ft.dwLowDateTime = static_cast(ll); + ft.dwHighDateTime = static_cast(ll >> 32); +} + +template +GHC_INLINE uintmax_t hard_links_from_INFO(const INFO* info) +{ + return static_cast(-1); +} + +template <> +GHC_INLINE uintmax_t hard_links_from_INFO(const BY_HANDLE_FILE_INFORMATION* info) +{ + return info->nNumberOfLinks; +} + +template +GHC_INLINE DWORD reparse_tag_from_INFO(const INFO*) +{ + return 0; +} + +template <> +GHC_INLINE DWORD reparse_tag_from_INFO(const WIN32_FIND_DATAW* info) +{ + return info->dwReserved0; +} + +template +GHC_INLINE file_status status_from_INFO(const path& p, const INFO* info, std::error_code& ec, uintmax_t* sz = nullptr, time_t* lwt = nullptr) +{ + file_type ft = file_type::unknown; + if (sizeof(INFO) == sizeof(WIN32_FIND_DATAW)) { + if (detail::reparse_tag_from_INFO(info) == IO_REPARSE_TAG_SYMLINK) { + ft = file_type::symlink; + } + } + else { + if ((info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { + auto reparseData = detail::getReparseData(p, ec); + if (!ec && reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag) && reparseData->ReparseTag == IO_REPARSE_TAG_SYMLINK) { + ft = file_type::symlink; + } + } + } + if (ft == file_type::unknown) { + if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + ft = file_type::directory; + } + else { + ft = file_type::regular; + } + } + perms prms = perms::owner_read | perms::group_read | perms::others_read; + if (!(info->dwFileAttributes & FILE_ATTRIBUTE_READONLY)) { + prms = prms | perms::owner_write | perms::group_write | perms::others_write; + } + if (has_executable_extension(p)) { + prms = prms | perms::owner_exec | perms::group_exec | perms::others_exec; + } + if (sz) { + *sz = static_cast(info->nFileSizeHigh) << (sizeof(info->nFileSizeHigh) * 8) | info->nFileSizeLow; + } + if (lwt) { + *lwt = detail::timeFromFILETIME(info->ftLastWriteTime); + } + return file_status(ft, prms); +} + +#endif + +GHC_INLINE bool is_not_found_error(std::error_code& ec) +{ +#ifdef GHC_OS_WINDOWS + return ec.value() == ERROR_FILE_NOT_FOUND || ec.value() == ERROR_PATH_NOT_FOUND || ec.value() == ERROR_INVALID_NAME; +#else + return ec.value() == ENOENT || ec.value() == ENOTDIR; +#endif +} + +GHC_INLINE file_status symlink_status_ex(const path& p, std::error_code& ec, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, time_t* lwt = nullptr) noexcept +{ +#ifdef GHC_OS_WINDOWS + file_status fs; + WIN32_FILE_ATTRIBUTE_DATA attr; + if (!GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { + ec = detail::make_system_error(); + } + else { + ec.clear(); + fs = detail::status_from_INFO(p, &attr, ec, sz, lwt); + if (nhl) { + *nhl = 0; + } + } + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found); + } + return ec ? file_status(file_type::none) : fs; +#else + (void)sz; + (void)nhl; + (void)lwt; + struct ::stat fs; + auto result = ::lstat(p.c_str(), &fs); + if (result == 0) { + ec.clear(); + file_status f_s = detail::file_status_from_st_mode(fs.st_mode); + return f_s; + } + ec = detail::make_system_error(); + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found, perms::unknown); + } + return file_status(file_type::none); +#endif +} + +GHC_INLINE file_status status_ex(const path& p, std::error_code& ec, file_status* sls = nullptr, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, time_t* lwt = nullptr, int recurse_count = 0) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (recurse_count > 16) { + ec = detail::make_system_error(0x2A9 /*ERROR_STOPPED_ON_SYMLINK*/); + return file_status(file_type::unknown); + } + WIN32_FILE_ATTRIBUTE_DATA attr; + if (!::GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { + ec = detail::make_system_error(); + } + else if (attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + auto reparseData = detail::getReparseData(p, ec); + if (!ec && reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag) && reparseData->ReparseTag == IO_REPARSE_TAG_SYMLINK) { + path target = resolveSymlink(p, ec); + file_status result; + if (!ec && !target.empty()) { + if (sls) { + *sls = status_from_INFO(p, &attr, ec); + } + return detail::status_ex(target, ec, nullptr, sz, nhl, lwt, recurse_count + 1); + } + return file_status(file_type::unknown); + } + } + if (ec) { + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found); + } + return file_status(file_type::none); + } + if (nhl) { + *nhl = 0; + } + return detail::status_from_INFO(p, &attr, ec, sz, lwt); +#else + (void)recurse_count; + struct ::stat st; + auto result = ::lstat(p.c_str(), &st); + if (result == 0) { + ec.clear(); + file_status fs = detail::file_status_from_st_mode(st.st_mode); + if (sls) { + *sls = fs; + } + if (fs.type() == file_type::symlink) { + result = ::stat(p.c_str(), &st); + if (result == 0) { + fs = detail::file_status_from_st_mode(st.st_mode); + } + else { + ec = detail::make_system_error(); + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found, perms::unknown); + } + return file_status(file_type::none); + } + } + if (sz) { + *sz = static_cast(st.st_size); + } + if (nhl) { + *nhl = st.st_nlink; + } + if (lwt) { + *lwt = st.st_mtime; + } + return fs; + } + else { + ec = detail::make_system_error(); + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found, perms::unknown); + } + return file_status(file_type::none); + } +#endif +} + +} // namespace detail + +GHC_INLINE u8arguments::u8arguments(int& argc, char**& argv) + : _argc(argc) + , _argv(argv) + , _refargc(argc) + , _refargv(argv) + , _isvalid(false) +{ +#ifdef GHC_OS_WINDOWS + LPWSTR* p; + p = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + _args.reserve(static_cast(argc)); + _argp.reserve(static_cast(argc)); + for (size_t i = 0; i < static_cast(argc); ++i) { + _args.push_back(detail::toUtf8(std::wstring(p[i]))); + _argp.push_back((char*)_args[i].data()); + } + argv = _argp.data(); + ::LocalFree(p); + _isvalid = true; +#else + std::setlocale(LC_ALL, ""); +#if defined(__ANDROID__) && __ANDROID_API__ < 26 + _isvalid = true; +#else + if (detail::equals_simple_insensitive(::nl_langinfo(CODESET), "UTF-8")) { + _isvalid = true; + } +#endif +#endif +} + +//----------------------------------------------------------------------------- +// [fs.path.construct] constructors and destructor + +GHC_INLINE path::path() noexcept {} + +GHC_INLINE path::path(const path& p) + : _path(p._path) +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + , _prefixLength(p._prefixLength) +#endif +{ +} + +GHC_INLINE path::path(path&& p) noexcept + : _path(std::move(p._path)) +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + , _prefixLength(p._prefixLength) +#endif +{ +} + +GHC_INLINE path::path(string_type&& source, format fmt) + : _path(std::move(source)) +{ + postprocess_path_with_format(fmt); +} + +#endif // GHC_EXPAND_IMPL + +#ifdef GHC_WITH_EXCEPTIONS +template +inline path::path(const Source& source, const std::locale& loc, format fmt) + : path(source, fmt) +{ + std::string locName = loc.name(); + if (!(locName.length() >= 5 && (locName.substr(locName.length() - 5) == "UTF-8" || locName.substr(locName.length() - 5) == "utf-8"))) { + throw filesystem_error("This implementation only supports UTF-8 locales!", path(_path), detail::make_error_code(detail::portable_error::not_supported)); + } +} + +template +inline path::path(InputIterator first, InputIterator last, const std::locale& loc, format fmt) + : path(std::basic_string::value_type>(first, last), fmt) +{ + std::string locName = loc.name(); + if (!(locName.length() >= 5 && (locName.substr(locName.length() - 5) == "UTF-8" || locName.substr(locName.length() - 5) == "utf-8"))) { + throw filesystem_error("This implementation only supports UTF-8 locales!", path(_path), detail::make_error_code(detail::portable_error::not_supported)); + } +} +#endif + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE path::~path() {} + +//----------------------------------------------------------------------------- +// [fs.path.assign] assignments + +GHC_INLINE path& path::operator=(const path& p) +{ + _path = p._path; +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = p._prefixLength; +#endif + return *this; +} + +GHC_INLINE path& path::operator=(path&& p) noexcept +{ + _path = std::move(p._path); +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = p._prefixLength; +#endif + return *this; +} + +GHC_INLINE path& path::operator=(path::string_type&& source) +{ + return assign(source); +} + +GHC_INLINE path& path::assign(path::string_type&& source) +{ + _path = std::move(source); + postprocess_path_with_format(native_format); + return *this; +} + +#endif // GHC_EXPAND_IMPL + +template +inline path& path::operator=(const Source& source) +{ + return assign(source); +} + +template +inline path& path::assign(const Source& source) +{ +#ifdef GHC_USE_WCHAR_T + _path.assign(detail::toWChar(source)); +#else + _path.assign(detail::toUtf8(source)); +#endif + postprocess_path_with_format(native_format); + return *this; +} + +template <> +inline path& path::assign(const path& source) +{ + _path = source._path; +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = source._prefixLength; +#endif + return *this; +} + +template +inline path& path::assign(InputIterator first, InputIterator last) +{ + _path.assign(first, last); + postprocess_path_with_format(native_format); + return *this; +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// [fs.path.append] appends + +GHC_INLINE path& path::operator/=(const path& p) +{ + if (p.empty()) { + // was: if ((!has_root_directory() && is_absolute()) || has_filename()) + if (!_path.empty() && _path[_path.length() - 1] != preferred_separator && _path[_path.length() - 1] != ':') { + _path += preferred_separator; + } + return *this; + } + if ((p.is_absolute() && (_path != root_name()._path || p._path != "/")) || (p.has_root_name() && p.root_name() != root_name())) { + assign(p); + return *this; + } + if (p.has_root_directory()) { + assign(root_name()); + } + else if ((!has_root_directory() && is_absolute()) || has_filename()) { + _path += preferred_separator; + } + auto iter = p.begin(); + bool first = true; + if (p.has_root_name()) { + ++iter; + } + while (iter != p.end()) { + if (!first && !(!_path.empty() && _path[_path.length() - 1] == preferred_separator)) { + _path += preferred_separator; + } + first = false; + _path += (*iter++).native(); + } + check_long_path(); + return *this; +} + +GHC_INLINE void path::append_name(const value_type* name) +{ + if (_path.empty()) { + this->operator/=(path(name)); + } + else { + if (_path.back() != path::preferred_separator) { + _path.push_back(path::preferred_separator); + } + _path += name; + check_long_path(); + } +} + +#endif // GHC_EXPAND_IMPL + +template +inline path& path::operator/=(const Source& source) +{ + return append(source); +} + +template +inline path& path::append(const Source& source) +{ + return this->operator/=(path(source)); +} + +template <> +inline path& path::append(const path& p) +{ + return this->operator/=(p); +} + +template +inline path& path::append(InputIterator first, InputIterator last) +{ + std::basic_string::value_type> part(first, last); + return append(part); +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// [fs.path.concat] concatenation + +GHC_INLINE path& path::operator+=(const path& x) +{ + return concat(x._path); +} + +GHC_INLINE path& path::operator+=(const string_type& x) +{ + return concat(x); +} + +#ifdef GHC_WITH_STRING_VIEW +GHC_INLINE path& path::operator+=(basic_string_view x) +{ + return concat(x); +} +#endif + +GHC_INLINE path& path::operator+=(const value_type* x) +{ +#ifdef GHC_WITH_STRING_VIEW + basic_string_view part(x); +#else + string_type part(x); +#endif + return concat(part); +} + +GHC_INLINE path& path::operator+=(value_type x) +{ +#ifdef GHC_OS_WINDOWS + if (x == generic_separator) { + x = preferred_separator; + } +#endif + if (_path.empty() || _path.back() != preferred_separator) { + _path += x; + } + check_long_path(); + return *this; +} + +#endif // GHC_EXPAND_IMPL + +template +inline path::path_from_string& path::operator+=(const Source& x) +{ + return concat(x); +} + +template +inline path::path_type_EcharT& path::operator+=(EcharT x) +{ +#ifdef GHC_WITH_STRING_VIEW + basic_string_view part(&x, 1); +#else + std::basic_string part(1, x); +#endif + concat(part); + return *this; +} + +template +inline path& path::concat(const Source& x) +{ + path p(x); + _path += p._path; + postprocess_path_with_format(native_format); + return *this; +} +template +inline path& path::concat(InputIterator first, InputIterator last) +{ + _path.append(first, last); + postprocess_path_with_format(native_format); + return *this; +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// [fs.path.modifiers] modifiers +GHC_INLINE void path::clear() noexcept +{ + _path.clear(); +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = 0; +#endif +} + +GHC_INLINE path& path::make_preferred() +{ + // as this filesystem implementation only uses generic_format + // internally, this must be a no-op + return *this; +} + +GHC_INLINE path& path::remove_filename() +{ + if (has_filename()) { + _path.erase(_path.size() - filename()._path.size()); + } + return *this; +} + +GHC_INLINE path& path::replace_filename(const path& replacement) +{ + remove_filename(); + return append(replacement); +} + +GHC_INLINE path& path::replace_extension(const path& replacement) +{ + if (has_extension()) { + _path.erase(_path.size() - extension()._path.size()); + } + if (!replacement.empty() && replacement._path[0] != '.') { + _path += '.'; + } + return concat(replacement); +} + +GHC_INLINE void path::swap(path& rhs) noexcept +{ + _path.swap(rhs._path); +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + std::swap(_prefixLength, rhs._prefixLength); +#endif +} + +//----------------------------------------------------------------------------- +// [fs.path.native.obs] native format observers +GHC_INLINE const path::string_type& path::native() const noexcept +{ + return _path; +} + +GHC_INLINE const path::value_type* path::c_str() const noexcept +{ + return native().c_str(); +} + +GHC_INLINE path::operator path::string_type() const +{ + return native(); +} + +#endif // GHC_EXPAND_IMPL + +template +inline std::basic_string path::string(const Allocator& a) const +{ +#ifdef GHC_USE_WCHAR_T + return detail::fromWChar>(_path, a); +#else + return detail::fromUtf8>(_path, a); +#endif +} + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE std::string path::string() const +{ +#ifdef GHC_USE_WCHAR_T + return detail::toUtf8(native()); +#else + return native(); +#endif +} + +GHC_INLINE std::wstring path::wstring() const +{ +#ifdef GHC_USE_WCHAR_T + return native(); +#else + return detail::fromUtf8(native()); +#endif +} + +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) +GHC_INLINE std::u8string path::u8string() const +{ +#ifdef GHC_USE_WCHAR_T + return std::u8string(reinterpret_cast(detail::toUtf8(native()).c_str())); +#else + return std::u8string(reinterpret_cast(c_str())); +#endif +} +#else +GHC_INLINE std::string path::u8string() const +{ +#ifdef GHC_USE_WCHAR_T + return detail::toUtf8(native()); +#else + return native(); +#endif +} +#endif + +GHC_INLINE std::u16string path::u16string() const +{ + // TODO: optimize + return detail::fromUtf8(string()); +} + +GHC_INLINE std::u32string path::u32string() const +{ + // TODO: optimize + return detail::fromUtf8(string()); +} + +#endif // GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// [fs.path.generic.obs] generic format observers +template +inline std::basic_string path::generic_string(const Allocator& a) const +{ +#ifdef GHC_OS_WINDOWS +#ifdef GHC_USE_WCHAR_T + auto result = detail::fromWChar, path::string_type>(_path, a); +#else + auto result = detail::fromUtf8>(_path, a); +#endif + for (auto& c : result) { + if (c == preferred_separator) { + c = generic_separator; + } + } + return result; +#else + return detail::fromUtf8>(_path, a); +#endif +} + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE std::string path::generic_string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return _path; +#endif +} + +GHC_INLINE std::wstring path::generic_wstring() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return detail::fromUtf8(_path); +#endif +} // namespace filesystem + +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) +GHC_INLINE std::u8string path::generic_u8string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return std::u8string(reinterpret_cast(_path.c_str())); +#endif +} +#else +GHC_INLINE std::string path::generic_u8string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return _path; +#endif +} +#endif + +GHC_INLINE std::u16string path::generic_u16string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return detail::fromUtf8(_path); +#endif +} + +GHC_INLINE std::u32string path::generic_u32string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return detail::fromUtf8(_path); +#endif +} + +//----------------------------------------------------------------------------- +// [fs.path.compare] compare +GHC_INLINE int path::compare(const path& p) const noexcept +{ +#ifdef LWG_2936_BEHAVIOUR + auto rnl1 = root_name_length(); + auto rnl2 = p.root_name_length(); +#ifdef GHC_OS_WINDOWS + auto rnc = detail::compare_simple_insensitive(_path.c_str(), rnl1, p._path.c_str(), rnl2); +#else + auto rnc = _path.compare(0, rnl1, p._path, 0, (std::min(rnl1, rnl2))); +#endif + if (rnc) { + return rnc; + } + bool hrd1 = has_root_directory(), hrd2 = p.has_root_directory(); + if (hrd1 != hrd2) { + return hrd1 ? 1 : -1; + } + if (hrd1) { + ++rnl1; + ++rnl2; + } + auto iter1 = _path.begin() + static_cast(rnl1); + auto iter2 = p._path.begin() + static_cast(rnl2); + while (iter1 != _path.end() && iter2 != p._path.end() && *iter1 == *iter2) { + ++iter1; + ++iter2; + } + if (iter1 == _path.end()) { + return iter2 == p._path.end() ? 0 : -1; + } + if (iter2 == p._path.end()) { + return 1; + } + if (*iter1 == preferred_separator) { + return -1; + } + if (*iter2 == preferred_separator) { + return 1; + } + return *iter1 < *iter2 ? -1 : 1; +#else // LWG_2936_BEHAVIOUR +#ifdef GHC_OS_WINDOWS + auto rnl1 = root_name_length(); + auto rnl2 = p.root_name_length(); + auto rnc = detail::compare_simple_insensitive(_path.c_str(), rnl1, p._path.c_str(), rnl2); + if (rnc) { + return rnc; + } + return _path.compare(rnl1, std::string::npos, p._path, rnl2, std::string::npos); +#else + return _path.compare(p._path); +#endif +#endif +} + +GHC_INLINE int path::compare(const string_type& s) const +{ + return compare(path(s)); +} + +#ifdef GHC_WITH_STRING_VIEW +GHC_INLINE int path::compare(basic_string_view s) const +{ + return compare(path(s)); +} +#endif + +GHC_INLINE int path::compare(const value_type* s) const +{ + return compare(path(s)); +} + +//----------------------------------------------------------------------------- +// [fs.path.decompose] decomposition +#ifdef GHC_OS_WINDOWS +GHC_INLINE void path::handle_prefixes() +{ +#if defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = 0; + if (_path.length() >= 6 && _path[2] == '?' && std::toupper(static_cast(_path[4])) >= 'A' && std::toupper(static_cast(_path[4])) <= 'Z' && _path[5] == ':') { + if (detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\"))) || detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\??\\")))) { + _prefixLength = 4; + } + } +#endif // GHC_WIN_AUTO_PREFIX_LONG_PATH +} +#endif + +GHC_INLINE path::string_type::size_type path::root_name_length() const noexcept +{ +#ifdef GHC_OS_WINDOWS + if (_path.length() >= _prefixLength + 2 && std::toupper(static_cast(_path[_prefixLength])) >= 'A' && std::toupper(static_cast(_path[_prefixLength])) <= 'Z' && _path[_prefixLength + 1] == ':') { + return 2; + } +#endif + if (_path.length() > _prefixLength + 2 && _path[_prefixLength] == preferred_separator && _path[_prefixLength + 1] == preferred_separator && _path[_prefixLength + 2] != preferred_separator && std::isprint(_path[_prefixLength + 2])) { + impl_string_type::size_type pos = _path.find(preferred_separator, _prefixLength + 3); + if (pos == impl_string_type::npos) { + return _path.length(); + } + else { + return pos; + } + } + return 0; +} + +GHC_INLINE path path::root_name() const +{ + return path(_path.substr(_prefixLength, root_name_length()), native_format); +} + +GHC_INLINE path path::root_directory() const +{ + if (has_root_directory()) { + static const path _root_dir(std::string(1, preferred_separator), native_format); + return _root_dir; + } + return path(); +} + +GHC_INLINE path path::root_path() const +{ + return path(root_name().string() + root_directory().string(), native_format); +} + +GHC_INLINE path path::relative_path() const +{ + auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); + return path(_path.substr((std::min)(rootPathLen, _path.length())), generic_format); +} + +GHC_INLINE path path::parent_path() const +{ + auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); + if (rootPathLen < _path.length()) { + if (empty()) { + return path(); + } + else { + auto piter = end(); + auto iter = piter.decrement(_path.end()); + if (iter > _path.begin() + static_cast(rootPathLen) && *iter != preferred_separator) { + --iter; + } + return path(_path.begin(), iter, native_format); + } + } + else { + return *this; + } +} + +GHC_INLINE path path::filename() const +{ + return !has_relative_path() ? path() : path(*--end()); +} + +GHC_INLINE path path::stem() const +{ + impl_string_type fn = filename().native(); + if (fn != "." && fn != "..") { + impl_string_type::size_type pos = fn.rfind('.'); + if (pos != impl_string_type::npos && pos > 0) { + return path{fn.substr(0, pos), native_format}; + } + } + return path{fn, native_format}; +} + +GHC_INLINE path path::extension() const +{ + if (has_relative_path()) { + auto iter = end(); + const auto& fn = *--iter; + impl_string_type::size_type pos = fn._path.rfind('.'); + if (pos != std::string::npos && pos > 0) { + return path(fn._path.substr(pos), native_format); + } + } + return path(); +} + +#ifdef GHC_OS_WINDOWS +namespace detail { +GHC_INLINE bool has_executable_extension(const path& p) +{ + if (p.has_relative_path()) { + auto iter = p.end(); + const auto& fn = *--iter; + auto pos = fn._path.find_last_of('.'); + if (pos == std::string::npos || pos == 0 || fn._path.length() - pos != 3) { + return false; + } + const path::value_type* ext = fn._path.c_str() + pos + 1; + if (detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("exe")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("cmd")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("bat")) || + detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("com"))) { + return true; + } + } + return false; +} +} // namespace detail +#endif + +//----------------------------------------------------------------------------- +// [fs.path.query] query +GHC_INLINE bool path::empty() const noexcept +{ + return _path.empty(); +} + +GHC_INLINE bool path::has_root_name() const +{ + return root_name_length() > 0; +} + +GHC_INLINE bool path::has_root_directory() const +{ + auto rootLen = _prefixLength + root_name_length(); + return (_path.length() > rootLen && _path[rootLen] == preferred_separator); +} + +GHC_INLINE bool path::has_root_path() const +{ + return has_root_name() || has_root_directory(); +} + +GHC_INLINE bool path::has_relative_path() const +{ + auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); + return rootPathLen < _path.length(); +} + +GHC_INLINE bool path::has_parent_path() const +{ + return !parent_path().empty(); +} + +GHC_INLINE bool path::has_filename() const +{ + return has_relative_path() && !filename().empty(); +} + +GHC_INLINE bool path::has_stem() const +{ + return !stem().empty(); +} + +GHC_INLINE bool path::has_extension() const +{ + return !extension().empty(); +} + +GHC_INLINE bool path::is_absolute() const +{ +#ifdef GHC_OS_WINDOWS + return has_root_name() && has_root_directory(); +#else + return has_root_directory(); +#endif +} + +GHC_INLINE bool path::is_relative() const +{ + return !is_absolute(); +} + +//----------------------------------------------------------------------------- +// [fs.path.gen] generation +GHC_INLINE path path::lexically_normal() const +{ + path dest; + bool lastDotDot = false; + for (string_type s : *this) { + if (s == ".") { + dest /= ""; + continue; + } + else if (s == ".." && !dest.empty()) { + auto root = root_path(); + if (dest == root) { + continue; + } + else if (*(--dest.end()) != "..") { + if (dest._path.back() == preferred_separator) { + dest._path.pop_back(); + } + dest.remove_filename(); + continue; + } + } + if (!(s.empty() && lastDotDot)) { + dest /= s; + } + lastDotDot = s == ".."; + } + if (dest.empty()) { + dest = "."; + } + return dest; +} + +GHC_INLINE path path::lexically_relative(const path& base) const +{ + if (root_name() != base.root_name() || is_absolute() != base.is_absolute() || (!has_root_directory() && base.has_root_directory())) { + return path(); + } + const_iterator a = begin(), b = base.begin(); + while (a != end() && b != base.end() && *a == *b) { + ++a; + ++b; + } + if (a == end() && b == base.end()) { + return path("."); + } + int count = 0; + for (const auto& element : input_iterator_range(b, base.end())) { + if (element != "." && element != "" && element != "..") { + ++count; + } + else if (element == "..") { + --count; + } + } + if (count < 0) { + return path(); + } + path result; + for (int i = 0; i < count; ++i) { + result /= ".."; + } + for (const auto& element : input_iterator_range(a, end())) { + result /= element; + } + return result; +} + +GHC_INLINE path path::lexically_proximate(const path& base) const +{ + path result = lexically_relative(base); + return result.empty() ? *this : result; +} + +//----------------------------------------------------------------------------- +// [fs.path.itr] iterators +GHC_INLINE path::iterator::iterator() {} + +GHC_INLINE path::iterator::iterator(const path& p, const impl_string_type::const_iterator& pos) + : _first(p._path.begin()) + , _last(p._path.end()) + , _prefix(_first + static_cast(p._prefixLength)) + , _root(p.has_root_directory() ? _first + static_cast(p._prefixLength + p.root_name_length()) : _last) + , _iter(pos) +{ + if (pos != _last) { + updateCurrent(); + } +} + +GHC_INLINE path::impl_string_type::const_iterator path::iterator::increment(const path::impl_string_type::const_iterator& pos) const +{ + path::impl_string_type::const_iterator i = pos; + bool fromStart = i == _first || i == _prefix; + if (i != _last) { + if (fromStart && i == _first && _prefix > _first) { + i = _prefix; + } + else if (*i++ == preferred_separator) { + // we can only sit on a slash if it is a network name or a root + if (i != _last && *i == preferred_separator) { + if (fromStart && !(i + 1 != _last && *(i + 1) == preferred_separator)) { + // leadind double slashes detected, treat this and the + // following until a slash as one unit + i = std::find(++i, _last, preferred_separator); + } + else { + // skip redundant slashes + while (i != _last && *i == preferred_separator) { + ++i; + } + } + } + } + else { + if (fromStart && i != _last && *i == ':') { + ++i; + } + else { + i = std::find(i, _last, preferred_separator); + } + } + } + return i; +} + +GHC_INLINE path::impl_string_type::const_iterator path::iterator::decrement(const path::impl_string_type::const_iterator& pos) const +{ + path::impl_string_type::const_iterator i = pos; + if (i != _first) { + --i; + // if this is now the root slash or the trailing slash, we are done, + // else check for network name + if (i != _root && (pos != _last || *i != preferred_separator)) { +#ifdef GHC_OS_WINDOWS + static const impl_string_type seps = GHC_PLATFORM_LITERAL("\\:"); + i = std::find_first_of(std::reverse_iterator(i), std::reverse_iterator(_first), seps.begin(), seps.end()).base(); + if (i > _first && *i == ':') { + i++; + } +#else + i = std::find(std::reverse_iterator(i), std::reverse_iterator(_first), preferred_separator).base(); +#endif + // Now we have to check if this is a network name + if (i - _first == 2 && *_first == preferred_separator && *(_first + 1) == preferred_separator) { + i -= 2; + } + } + } + return i; +} + +GHC_INLINE void path::iterator::updateCurrent() +{ + if ((_iter == _last) || (_iter != _first && _iter != _last && (*_iter == preferred_separator && _iter != _root) && (_iter + 1 == _last))) { + _current.clear(); + } + else { + _current.assign(_iter, increment(_iter)); + } +} + +GHC_INLINE path::iterator& path::iterator::operator++() +{ + _iter = increment(_iter); + while (_iter != _last && // we didn't reach the end + _iter != _root && // this is not a root position + *_iter == preferred_separator && // we are on a separator + (_iter + 1) != _last // the slash is not the last char + ) { + ++_iter; + } + updateCurrent(); + return *this; +} + +GHC_INLINE path::iterator path::iterator::operator++(int) +{ + path::iterator i{*this}; + ++(*this); + return i; +} + +GHC_INLINE path::iterator& path::iterator::operator--() +{ + _iter = decrement(_iter); + updateCurrent(); + return *this; +} + +GHC_INLINE path::iterator path::iterator::operator--(int) +{ + auto i = *this; + --(*this); + return i; +} + +GHC_INLINE bool path::iterator::operator==(const path::iterator& other) const +{ + return _iter == other._iter; +} + +GHC_INLINE bool path::iterator::operator!=(const path::iterator& other) const +{ + return _iter != other._iter; +} + +GHC_INLINE path::iterator::reference path::iterator::operator*() const +{ + return _current; +} + +GHC_INLINE path::iterator::pointer path::iterator::operator->() const +{ + return &_current; +} + +GHC_INLINE path::iterator path::begin() const +{ + return iterator(*this, _path.begin()); +} + +GHC_INLINE path::iterator path::end() const +{ + return iterator(*this, _path.end()); +} + +//----------------------------------------------------------------------------- +// [fs.path.nonmember] path non-member functions +GHC_INLINE void swap(path& lhs, path& rhs) noexcept +{ + swap(lhs._path, rhs._path); +} + +GHC_INLINE size_t hash_value(const path& p) noexcept +{ + return std::hash()(p.generic_string()); +} + +#ifdef GHC_HAS_THREEWAY_COMP +GHC_INLINE std::strong_ordering operator<=>(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) <=> 0; +} +#endif + +GHC_INLINE bool operator==(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) == 0; +} + +GHC_INLINE bool operator!=(const path& lhs, const path& rhs) noexcept +{ + return !(lhs == rhs); +} + +GHC_INLINE bool operator<(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) < 0; +} + +GHC_INLINE bool operator<=(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) <= 0; +} + +GHC_INLINE bool operator>(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) > 0; +} + +GHC_INLINE bool operator>=(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) >= 0; +} + +GHC_INLINE path operator/(const path& lhs, const path& rhs) +{ + path result(lhs); + result /= rhs; + return result; +} + +#endif // GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// [fs.path.io] path inserter and extractor +template +inline std::basic_ostream& operator<<(std::basic_ostream& os, const path& p) +{ + os << "\""; + auto ps = p.string(); + for (auto c : ps) { + if (c == '"' || c == '\\') { + os << '\\'; + } + os << c; + } + os << "\""; + return os; +} + +template +inline std::basic_istream& operator>>(std::basic_istream& is, path& p) +{ + std::basic_string tmp; + charT c; + is >> c; + if (c == '"') { + auto sf = is.flags(); + is >> std::noskipws; + while (is) { + auto c2 = is.get(); + if (is) { + if (c2 == '\\') { + c2 = is.get(); + if (is) { + tmp += static_cast(c2); + } + } + else if (c2 == '"') { + break; + } + else { + tmp += static_cast(c2); + } + } + } + if ((sf & std::ios_base::skipws) == std::ios_base::skipws) { + is >> std::skipws; + } + p = path(tmp); + } + else { + is >> tmp; + p = path(static_cast(c) + tmp); + } + return is; +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// [fs.class.filesystem_error] Class filesystem_error +GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, std::error_code ec) + : std::system_error(ec, what_arg) + , _what_arg(what_arg) + , _ec(ec) +{ +} + +GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, const path& p1, std::error_code ec) + : std::system_error(ec, what_arg) + , _what_arg(what_arg) + , _ec(ec) + , _p1(p1) +{ + if (!_p1.empty()) { + _what_arg += ": '" + _p1.string() + "'"; + } +} + +GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, const path& p1, const path& p2, std::error_code ec) + : std::system_error(ec, what_arg) + , _what_arg(what_arg) + , _ec(ec) + , _p1(p1) + , _p2(p2) +{ + if (!_p1.empty()) { + _what_arg += ": '" + _p1.string() + "'"; + } + if (!_p2.empty()) { + _what_arg += ", '" + _p2.string() + "'"; + } +} + +GHC_INLINE const path& filesystem_error::path1() const noexcept +{ + return _p1; +} + +GHC_INLINE const path& filesystem_error::path2() const noexcept +{ + return _p2; +} + +GHC_INLINE const char* filesystem_error::what() const noexcept +{ + return _what_arg.c_str(); +} + +//----------------------------------------------------------------------------- +// [fs.op.funcs] filesystem operations +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path absolute(const path& p) +{ + std::error_code ec; + path result = absolute(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path absolute(const path& p, std::error_code& ec) +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (p.empty()) { + return absolute(current_path(ec), ec) / ""; + } + ULONG size = ::GetFullPathNameW(GHC_NATIVEWP(p), 0, 0, 0); + if (size) { + std::vector buf(size, 0); + ULONG s2 = GetFullPathNameW(GHC_NATIVEWP(p), size, buf.data(), nullptr); + if (s2 && s2 < size) { + path result = path(std::wstring(buf.data(), s2)); + if (p.filename() == ".") { + result /= "."; + } + return result; + } + } + ec = detail::make_system_error(); + return path(); +#else + path base = current_path(ec); + if (!ec) { + if (p.empty()) { + return base / p; + } + if (p.has_root_name()) { + if (p.has_root_directory()) { + return p; + } + else { + return p.root_name() / base.root_directory() / base.relative_path() / p.relative_path(); + } + } + else { + if (p.has_root_directory()) { + return base.root_name() / p; + } + else { + return base / p; + } + } + } + ec = detail::make_system_error(); + return path(); +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path canonical(const path& p) +{ + std::error_code ec; + auto result = canonical(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path canonical(const path& p, std::error_code& ec) +{ + if (p.empty()) { + ec = detail::make_error_code(detail::portable_error::not_found); + return path(); + } + path work = p.is_absolute() ? p : absolute(p, ec); + path result; + + auto fs = status(work, ec); + if (ec) { + return path(); + } + if (fs.type() == file_type::not_found) { + ec = detail::make_error_code(detail::portable_error::not_found); + return path(); + } + bool redo; + do { + auto rootPathLen = work._prefixLength + work.root_name_length() + (work.has_root_directory() ? 1 : 0); + redo = false; + result.clear(); + for (auto pe : work) { + if (pe.empty() || pe == ".") { + continue; + } + else if (pe == "..") { + result = result.parent_path(); + continue; + } + else if ((result / pe).string().length() <= rootPathLen) { + result /= pe; + continue; + } + auto sls = symlink_status(result / pe, ec); + if (ec) { + return path(); + } + if (is_symlink(sls)) { + redo = true; + auto target = read_symlink(result / pe, ec); + if (ec) { + return path(); + } + if (target.is_absolute()) { + result = target; + continue; + } + else { + result /= target; + continue; + } + } + else { + result /= pe; + } + } + work = result; + } while (redo); + ec.clear(); + return result; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void copy(const path& from, const path& to) +{ + copy(from, to, copy_options::none); +} + +GHC_INLINE void copy(const path& from, const path& to, copy_options options) +{ + std::error_code ec; + copy(from, to, options, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); + } +} +#endif + +GHC_INLINE void copy(const path& from, const path& to, std::error_code& ec) noexcept +{ + copy(from, to, copy_options::none, ec); +} + +GHC_INLINE void copy(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept +{ + std::error_code tec; + file_status fs_from, fs_to; + ec.clear(); + if ((options & (copy_options::skip_symlinks | copy_options::copy_symlinks | copy_options::create_symlinks)) != copy_options::none) { + fs_from = symlink_status(from, ec); + } + else { + fs_from = status(from, ec); + } + if (!exists(fs_from)) { + if (!ec) { + ec = detail::make_error_code(detail::portable_error::not_found); + } + return; + } + if ((options & (copy_options::skip_symlinks | copy_options::create_symlinks)) != copy_options::none) { + fs_to = symlink_status(to, tec); + } + else { + fs_to = status(to, tec); + } + if (is_other(fs_from) || is_other(fs_to) || (is_directory(fs_from) && is_regular_file(fs_to)) || (exists(fs_to) && equivalent(from, to, ec))) { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + } + else if (is_symlink(fs_from)) { + if ((options & copy_options::skip_symlinks) == copy_options::none) { + if (!exists(fs_to) && (options & copy_options::copy_symlinks) != copy_options::none) { + copy_symlink(from, to, ec); + } + else { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + } + } + } + else if (is_regular_file(fs_from)) { + if ((options & copy_options::directories_only) == copy_options::none) { + if ((options & copy_options::create_symlinks) != copy_options::none) { + create_symlink(from.is_absolute() ? from : canonical(from, ec), to, ec); + } +#ifndef GHC_OS_WEB + else if ((options & copy_options::create_hard_links) != copy_options::none) { + create_hard_link(from, to, ec); + } +#endif + else if (is_directory(fs_to)) { + copy_file(from, to / from.filename(), options, ec); + } + else { + copy_file(from, to, options, ec); + } + } + } +#ifdef LWG_2682_BEHAVIOUR + else if (is_directory(fs_from) && (options & copy_options::create_symlinks) != copy_options::none) { + ec = detail::make_error_code(detail::portable_error::is_a_directory); + } +#endif + else if (is_directory(fs_from) && (options == copy_options::none || (options & copy_options::recursive) != copy_options::none)) { + if (!exists(fs_to)) { + create_directory(to, from, ec); + if (ec) { + return; + } + } + for (auto iter = directory_iterator(from, ec); iter != directory_iterator(); iter.increment(ec)) { + if (!ec) { + copy(iter->path(), to / iter->path().filename(), options | static_cast(0x8000), ec); + } + if (ec) { + return; + } + } + } + return; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool copy_file(const path& from, const path& to) +{ + return copy_file(from, to, copy_options::none); +} + +GHC_INLINE bool copy_file(const path& from, const path& to, copy_options option) +{ + std::error_code ec; + auto result = copy_file(from, to, option, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); + } + return result; +} +#endif + +GHC_INLINE bool copy_file(const path& from, const path& to, std::error_code& ec) noexcept +{ + return copy_file(from, to, copy_options::none, ec); +} + +GHC_INLINE bool copy_file(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept +{ + std::error_code tecf, tect; + auto sf = status(from, tecf); + auto st = status(to, tect); + bool overwrite = false; + ec.clear(); + if (!is_regular_file(sf)) { + ec = tecf; + return false; + } + if (exists(st) && (!is_regular_file(st) || equivalent(from, to, ec) || (options & (copy_options::skip_existing | copy_options::overwrite_existing | copy_options::update_existing)) == copy_options::none)) { + ec = tect ? tect : detail::make_error_code(detail::portable_error::exists); + return false; + } + if (exists(st)) { + if ((options & copy_options::update_existing) == copy_options::update_existing) { + auto from_time = last_write_time(from, ec); + if (ec) { + ec = detail::make_system_error(); + return false; + } + auto to_time = last_write_time(to, ec); + if (ec) { + ec = detail::make_system_error(); + return false; + } + if (from_time <= to_time) { + return false; + } + } + overwrite = true; + } +#ifdef GHC_OS_WINDOWS + if (!::CopyFileW(GHC_NATIVEWP(from), GHC_NATIVEWP(to), !overwrite)) { + ec = detail::make_system_error(); + return false; + } + return true; +#else + std::vector buffer(16384, '\0'); + int in = -1, out = -1; + if ((in = ::open(from.c_str(), O_RDONLY)) < 0) { + ec = detail::make_system_error(); + return false; + } + int mode = O_CREAT | O_WRONLY | O_TRUNC; + if (!overwrite) { + mode |= O_EXCL; + } + if ((out = ::open(to.c_str(), mode, static_cast(sf.permissions() & perms::all))) < 0) { + ec = detail::make_system_error(); + ::close(in); + return false; + } + ssize_t br, bw; + while ((br = ::read(in, buffer.data(), buffer.size())) > 0) { + ssize_t offset = 0; + do { + if ((bw = ::write(out, buffer.data() + offset, static_cast(br))) > 0) { + br -= bw; + offset += bw; + } + else if (bw < 0) { + ec = detail::make_system_error(); + ::close(in); + ::close(out); + return false; + } + } while (br); + } + ::close(in); + ::close(out); + return true; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void copy_symlink(const path& existing_symlink, const path& new_symlink) +{ + std::error_code ec; + copy_symlink(existing_symlink, new_symlink, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), existing_symlink, new_symlink, ec); + } +} +#endif + +GHC_INLINE void copy_symlink(const path& existing_symlink, const path& new_symlink, std::error_code& ec) noexcept +{ + ec.clear(); + auto to = read_symlink(existing_symlink, ec); + if (!ec) { + if (exists(to, ec) && is_directory(to, ec)) { + create_directory_symlink(to, new_symlink, ec); + } + else { + create_symlink(to, new_symlink, ec); + } + } +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool create_directories(const path& p) +{ + std::error_code ec; + auto result = create_directories(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool create_directories(const path& p, std::error_code& ec) noexcept +{ + path current; + ec.clear(); + bool didCreate = false; + auto rootPathLen = p._prefixLength + p.root_name_length() + (p.has_root_directory() ? 1 : 0); + current = p.native().substr(0, rootPathLen); + path folders(p._path.substr(rootPathLen)); + for (path::string_type part : folders) { + current /= part; + std::error_code tec; + auto fs = status(current, tec); + if (tec && fs.type() != file_type::not_found) { + ec = tec; + return false; + } + if (!exists(fs)) { + create_directory(current, ec); + if (ec) { + std::error_code tmp_ec; + if (is_directory(current, tmp_ec)) { + ec.clear(); + } + else { + return false; + } + } + didCreate = true; + } +#ifndef LWG_2935_BEHAVIOUR + else if (!is_directory(fs)) { + ec = detail::make_error_code(detail::portable_error::exists); + return false; + } +#endif + } + return didCreate; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool create_directory(const path& p) +{ + std::error_code ec; + auto result = create_directory(p, path(), ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool create_directory(const path& p, std::error_code& ec) noexcept +{ + return create_directory(p, path(), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool create_directory(const path& p, const path& attributes) +{ + std::error_code ec; + auto result = create_directory(p, attributes, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool create_directory(const path& p, const path& attributes, std::error_code& ec) noexcept +{ + std::error_code tec; + ec.clear(); + auto fs = status(p, tec); +#ifdef LWG_2935_BEHAVIOUR + if (status_known(fs) && exists(fs)) { + return false; + } +#else + if (status_known(fs) && exists(fs) && is_directory(fs)) { + return false; + } +#endif +#ifdef GHC_OS_WINDOWS + if (!attributes.empty()) { + if (!::CreateDirectoryExW(GHC_NATIVEWP(attributes), GHC_NATIVEWP(p), NULL)) { + ec = detail::make_system_error(); + return false; + } + } + else if (!::CreateDirectoryW(GHC_NATIVEWP(p), NULL)) { + ec = detail::make_system_error(); + return false; + } +#else + ::mode_t attribs = static_cast(perms::all); + if (!attributes.empty()) { + struct ::stat fileStat; + if (::stat(attributes.c_str(), &fileStat) != 0) { + ec = detail::make_system_error(); + return false; + } + attribs = fileStat.st_mode; + } + if (::mkdir(p.c_str(), attribs) != 0) { + ec = detail::make_system_error(); + return false; + } +#endif + return true; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void create_directory_symlink(const path& to, const path& new_symlink) +{ + std::error_code ec; + create_directory_symlink(to, new_symlink, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), to, new_symlink, ec); + } +} +#endif + +GHC_INLINE void create_directory_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept +{ + detail::create_symlink(to, new_symlink, true, ec); +} + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void create_hard_link(const path& to, const path& new_hard_link) +{ + std::error_code ec; + create_hard_link(to, new_hard_link, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), to, new_hard_link, ec); + } +} +#endif + +GHC_INLINE void create_hard_link(const path& to, const path& new_hard_link, std::error_code& ec) noexcept +{ + detail::create_hardlink(to, new_hard_link, ec); +} +#endif + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void create_symlink(const path& to, const path& new_symlink) +{ + std::error_code ec; + create_symlink(to, new_symlink, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), to, new_symlink, ec); + } +} +#endif + +GHC_INLINE void create_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept +{ + detail::create_symlink(to, new_symlink, false, ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path current_path() +{ + std::error_code ec; + auto result = current_path(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), ec); + } + return result; +} +#endif + +GHC_INLINE path current_path(std::error_code& ec) +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + DWORD pathlen = ::GetCurrentDirectoryW(0, 0); + std::unique_ptr buffer(new wchar_t[size_t(pathlen) + 1]); + if (::GetCurrentDirectoryW(pathlen, buffer.get()) == 0) { + ec = detail::make_system_error(); + return path(); + } + return path(std::wstring(buffer.get()), path::native_format); +#else + size_t pathlen = static_cast(std::max(int(::pathconf(".", _PC_PATH_MAX)), int(PATH_MAX))); + std::unique_ptr buffer(new char[pathlen + 1]); + if (::getcwd(buffer.get(), pathlen) == nullptr) { + ec = detail::make_system_error(); + return path(); + } + return path(buffer.get()); +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void current_path(const path& p) +{ + std::error_code ec; + current_path(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void current_path(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (!::SetCurrentDirectoryW(GHC_NATIVEWP(p))) { + ec = detail::make_system_error(); + } +#else + if (::chdir(p.string().c_str()) == -1) { + ec = detail::make_system_error(); + } +#endif +} + +GHC_INLINE bool exists(file_status s) noexcept +{ + return status_known(s) && s.type() != file_type::not_found; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool exists(const path& p) +{ + return exists(status(p)); +} +#endif + +GHC_INLINE bool exists(const path& p, std::error_code& ec) noexcept +{ + file_status s = status(p, ec); + if (status_known(s)) { + ec.clear(); + } + return exists(s); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool equivalent(const path& p1, const path& p2) +{ + std::error_code ec; + bool result = equivalent(p1, p2, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p1, p2, ec); + } + return result; +} +#endif + +GHC_INLINE bool equivalent(const path& p1, const path& p2, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + detail::unique_handle file1(::CreateFileW(GHC_NATIVEWP(p1), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)); + auto e1 = ::GetLastError(); + detail::unique_handle file2(::CreateFileW(GHC_NATIVEWP(p2), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)); + if (!file1 || !file2) { +#ifdef LWG_2937_BEHAVIOUR + ec = detail::make_system_error(e1 ? e1 : ::GetLastError()); +#else + if (file1 == file2) { + ec = detail::make_system_error(e1 ? e1 : ::GetLastError()); + } +#endif + return false; + } + BY_HANDLE_FILE_INFORMATION inf1, inf2; + if (!::GetFileInformationByHandle(file1.get(), &inf1)) { + ec = detail::make_system_error(); + return false; + } + if (!::GetFileInformationByHandle(file2.get(), &inf2)) { + ec = detail::make_system_error(); + return false; + } + return inf1.ftLastWriteTime.dwLowDateTime == inf2.ftLastWriteTime.dwLowDateTime && inf1.ftLastWriteTime.dwHighDateTime == inf2.ftLastWriteTime.dwHighDateTime && inf1.nFileIndexHigh == inf2.nFileIndexHigh && inf1.nFileIndexLow == inf2.nFileIndexLow && + inf1.nFileSizeHigh == inf2.nFileSizeHigh && inf1.nFileSizeLow == inf2.nFileSizeLow && inf1.dwVolumeSerialNumber == inf2.dwVolumeSerialNumber; +#else + struct ::stat s1, s2; + auto rc1 = ::stat(p1.c_str(), &s1); + auto e1 = errno; + auto rc2 = ::stat(p2.c_str(), &s2); + if (rc1 || rc2) { +#ifdef LWG_2937_BEHAVIOUR + ec = detail::make_system_error(e1 ? e1 : errno); +#else + if (rc1 && rc2) { + ec = detail::make_system_error(e1 ? e1 : errno); + } +#endif + return false; + } + return s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino && s1.st_size == s2.st_size && s1.st_mtime == s2.st_mtime; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t file_size(const path& p) +{ + std::error_code ec; + auto result = file_size(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE uintmax_t file_size(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + WIN32_FILE_ATTRIBUTE_DATA attr; + if (!GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { + ec = detail::make_system_error(); + return static_cast(-1); + } + return static_cast(attr.nFileSizeHigh) << (sizeof(attr.nFileSizeHigh) * 8) | attr.nFileSizeLow; +#else + struct ::stat fileStat; + if (::stat(p.c_str(), &fileStat) == -1) { + ec = detail::make_system_error(); + return static_cast(-1); + } + return static_cast(fileStat.st_size); +#endif +} + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t hard_link_count(const path& p) +{ + std::error_code ec; + auto result = hard_link_count(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + uintmax_t result = static_cast(-1); + detail::unique_handle file(::CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)); + BY_HANDLE_FILE_INFORMATION inf; + if (!file) { + ec = detail::make_system_error(); + } + else { + if (!::GetFileInformationByHandle(file.get(), &inf)) { + ec = detail::make_system_error(); + } + else { + result = inf.nNumberOfLinks; + } + } + return result; +#else + uintmax_t result = 0; + file_status fs = detail::status_ex(p, ec, nullptr, nullptr, &result, nullptr); + if (fs.type() == file_type::not_found) { + ec = detail::make_error_code(detail::portable_error::not_found); + } + return ec ? static_cast(-1) : result; +#endif +} +#endif + +GHC_INLINE bool is_block_file(file_status s) noexcept +{ + return s.type() == file_type::block; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_block_file(const path& p) +{ + return is_block_file(status(p)); +} +#endif + +GHC_INLINE bool is_block_file(const path& p, std::error_code& ec) noexcept +{ + return is_block_file(status(p, ec)); +} + +GHC_INLINE bool is_character_file(file_status s) noexcept +{ + return s.type() == file_type::character; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_character_file(const path& p) +{ + return is_character_file(status(p)); +} +#endif + +GHC_INLINE bool is_character_file(const path& p, std::error_code& ec) noexcept +{ + return is_character_file(status(p, ec)); +} + +GHC_INLINE bool is_directory(file_status s) noexcept +{ + return s.type() == file_type::directory; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_directory(const path& p) +{ + return is_directory(status(p)); +} +#endif + +GHC_INLINE bool is_directory(const path& p, std::error_code& ec) noexcept +{ + return is_directory(status(p, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_empty(const path& p) +{ + if (is_directory(p)) { + return directory_iterator(p) == directory_iterator(); + } + else { + return file_size(p) == 0; + } +} +#endif + +GHC_INLINE bool is_empty(const path& p, std::error_code& ec) noexcept +{ + auto fs = status(p, ec); + if (ec) { + return false; + } + if (is_directory(fs)) { + directory_iterator iter(p, ec); + if (ec) { + return false; + } + return iter == directory_iterator(); + } + else { + auto sz = file_size(p, ec); + if (ec) { + return false; + } + return sz == 0; + } +} + +GHC_INLINE bool is_fifo(file_status s) noexcept +{ + return s.type() == file_type::fifo; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_fifo(const path& p) +{ + return is_fifo(status(p)); +} +#endif + +GHC_INLINE bool is_fifo(const path& p, std::error_code& ec) noexcept +{ + return is_fifo(status(p, ec)); +} + +GHC_INLINE bool is_other(file_status s) noexcept +{ + return exists(s) && !is_regular_file(s) && !is_directory(s) && !is_symlink(s); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_other(const path& p) +{ + return is_other(status(p)); +} +#endif + +GHC_INLINE bool is_other(const path& p, std::error_code& ec) noexcept +{ + return is_other(status(p, ec)); +} + +GHC_INLINE bool is_regular_file(file_status s) noexcept +{ + return s.type() == file_type::regular; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_regular_file(const path& p) +{ + return is_regular_file(status(p)); +} +#endif + +GHC_INLINE bool is_regular_file(const path& p, std::error_code& ec) noexcept +{ + return is_regular_file(status(p, ec)); +} + +GHC_INLINE bool is_socket(file_status s) noexcept +{ + return s.type() == file_type::socket; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_socket(const path& p) +{ + return is_socket(status(p)); +} +#endif + +GHC_INLINE bool is_socket(const path& p, std::error_code& ec) noexcept +{ + return is_socket(status(p, ec)); +} + +GHC_INLINE bool is_symlink(file_status s) noexcept +{ + return s.type() == file_type::symlink; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_symlink(const path& p) +{ + return is_symlink(symlink_status(p)); +} +#endif + +GHC_INLINE bool is_symlink(const path& p, std::error_code& ec) noexcept +{ + return is_symlink(symlink_status(p, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_time_type last_write_time(const path& p) +{ + std::error_code ec; + auto result = last_write_time(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE file_time_type last_write_time(const path& p, std::error_code& ec) noexcept +{ + time_t result = 0; + ec.clear(); + file_status fs = detail::status_ex(p, ec, nullptr, nullptr, nullptr, &result); + return ec ? (file_time_type::min)() : std::chrono::system_clock::from_time_t(result); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void last_write_time(const path& p, file_time_type new_time) +{ + std::error_code ec; + last_write_time(p, new_time, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void last_write_time(const path& p, file_time_type new_time, std::error_code& ec) noexcept +{ + ec.clear(); + auto d = new_time.time_since_epoch(); +#ifdef GHC_OS_WINDOWS + detail::unique_handle file(::CreateFileW(GHC_NATIVEWP(p), FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL)); + FILETIME ft; + auto tt = std::chrono::duration_cast(d).count() * 10 + 116444736000000000; + ft.dwLowDateTime = static_cast(tt); + ft.dwHighDateTime = static_cast(tt >> 32); + if (!::SetFileTime(file.get(), 0, 0, &ft)) { + ec = detail::make_system_error(); + } +#elif defined(GHC_OS_MACOS) +#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED +#if __MAC_OS_X_VERSION_MIN_REQUIRED < 101300 + struct ::stat fs; + if (::stat(p.c_str(), &fs) == 0) { + struct ::timeval tv[2]; + tv[0].tv_sec = fs.st_atimespec.tv_sec; + tv[0].tv_usec = static_cast(fs.st_atimespec.tv_nsec / 1000); + tv[1].tv_sec = std::chrono::duration_cast(d).count(); + tv[1].tv_usec = static_cast(std::chrono::duration_cast(d).count() % 1000000); + if (::utimes(p.c_str(), tv) == 0) { + return; + } + } + ec = detail::make_system_error(); + return; +#else + struct ::timespec times[2]; + times[0].tv_sec = 0; + times[0].tv_nsec = UTIME_OMIT; + times[1].tv_sec = std::chrono::duration_cast(d).count(); + times[1].tv_nsec = 0; // std::chrono::duration_cast(d).count() % 1000000000; + if (::utimensat(AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { + ec = detail::make_system_error(); + } + return; +#endif +#endif +#else +#ifndef UTIME_OMIT +#define UTIME_OMIT ((1l << 30) - 2l) +#endif + struct ::timespec times[2]; + times[0].tv_sec = 0; + times[0].tv_nsec = UTIME_OMIT; + times[1].tv_sec = static_cast(std::chrono::duration_cast(d).count()); + times[1].tv_nsec = static_cast(std::chrono::duration_cast(d).count() % 1000000000); +#if defined(__ANDROID_API__) && __ANDROID_API__ < 12 + if (syscall(__NR_utimensat, AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { +#else + if (::utimensat((int)AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { +#endif + ec = detail::make_system_error(); + } + return; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void permissions(const path& p, perms prms, perm_options opts) +{ + std::error_code ec; + permissions(p, prms, opts, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void permissions(const path& p, perms prms, std::error_code& ec) noexcept +{ + permissions(p, prms, perm_options::replace, ec); +} + +GHC_INLINE void permissions(const path& p, perms prms, perm_options opts, std::error_code& ec) noexcept +{ + if (static_cast(opts & (perm_options::replace | perm_options::add | perm_options::remove)) == 0) { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + return; + } + auto fs = symlink_status(p, ec); + if ((opts & perm_options::replace) != perm_options::replace) { + if ((opts & perm_options::add) == perm_options::add) { + prms = fs.permissions() | prms; + } + else { + prms = fs.permissions() & ~prms; + } + } +#ifdef GHC_OS_WINDOWS +#ifdef __GNUC__ + auto oldAttr = GetFileAttributesW(GHC_NATIVEWP(p)); + if (oldAttr != INVALID_FILE_ATTRIBUTES) { + DWORD newAttr = ((prms & perms::owner_write) == perms::owner_write) ? oldAttr & ~(static_cast(FILE_ATTRIBUTE_READONLY)) : oldAttr | FILE_ATTRIBUTE_READONLY; + if (oldAttr == newAttr || SetFileAttributesW(GHC_NATIVEWP(p), newAttr)) { + return; + } + } + ec = detail::make_system_error(); +#else + int mode = 0; + if ((prms & perms::owner_read) == perms::owner_read) { + mode |= _S_IREAD; + } + if ((prms & perms::owner_write) == perms::owner_write) { + mode |= _S_IWRITE; + } + if (::_wchmod(p.wstring().c_str(), mode) != 0) { + ec = detail::make_system_error(); + } +#endif +#else + if ((opts & perm_options::nofollow) != perm_options::nofollow) { + if (::chmod(p.c_str(), static_cast(prms)) != 0) { + ec = detail::make_system_error(); + } + } +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path proximate(const path& p, std::error_code& ec) +{ + auto cp = current_path(ec); + if (!ec) { + return proximate(p, cp, ec); + } + return path(); +} +#endif + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path proximate(const path& p, const path& base) +{ + return weakly_canonical(p).lexically_proximate(weakly_canonical(base)); +} +#endif + +GHC_INLINE path proximate(const path& p, const path& base, std::error_code& ec) +{ + return weakly_canonical(p, ec).lexically_proximate(weakly_canonical(base, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path read_symlink(const path& p) +{ + std::error_code ec; + auto result = read_symlink(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path read_symlink(const path& p, std::error_code& ec) +{ + file_status fs = symlink_status(p, ec); + if (fs.type() != file_type::symlink) { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + return path(); + } + auto result = detail::resolveSymlink(p, ec); + return ec ? path() : result; +} + +GHC_INLINE path relative(const path& p, std::error_code& ec) +{ + return relative(p, current_path(ec), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path relative(const path& p, const path& base) +{ + return weakly_canonical(p).lexically_relative(weakly_canonical(base)); +} +#endif + +GHC_INLINE path relative(const path& p, const path& base, std::error_code& ec) +{ + return weakly_canonical(p, ec).lexically_relative(weakly_canonical(base, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool remove(const path& p) +{ + std::error_code ec; + auto result = remove(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool remove(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS +#ifdef GHC_USE_WCHAR_T + auto cstr = p.c_str(); +#else + std::wstring np = detail::fromUtf8(p.u8string()); + auto cstr = np.c_str(); +#endif + DWORD attr = GetFileAttributesW(cstr); + if (attr == INVALID_FILE_ATTRIBUTES) { + auto error = ::GetLastError(); + if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND) { + return false; + } + ec = detail::make_system_error(error); + } + else if (attr & FILE_ATTRIBUTE_READONLY) { + auto new_attr = attr & ~static_cast(FILE_ATTRIBUTE_READONLY); + if (!SetFileAttributesW(cstr, new_attr)) { + auto error = ::GetLastError(); + ec = detail::make_system_error(error); + } + } + if (!ec) { + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + if (!RemoveDirectoryW(cstr)) { + ec = detail::make_system_error(); + } + } + else { + if (!DeleteFileW(cstr)) { + ec = detail::make_system_error(); + } + } + } +#else + if (::remove(p.c_str()) == -1) { + auto error = errno; + if (error == ENOENT) { + return false; + } + ec = detail::make_system_error(); + } +#endif + return ec ? false : true; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t remove_all(const path& p) +{ + std::error_code ec; + auto result = remove_all(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE uintmax_t remove_all(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); + uintmax_t count = 0; + if (p == "/") { + ec = detail::make_error_code(detail::portable_error::not_supported); + return static_cast(-1); + } + std::error_code tec; + auto fs = symlink_status(p, tec); + if (exists(fs) && is_directory(fs)) { + for (auto iter = directory_iterator(p, ec); iter != directory_iterator(); iter.increment(ec)) { + if (ec && !detail::is_not_found_error(ec)) { + break; + } + bool is_symlink_result = iter->is_symlink(ec); + if (ec) + return static_cast(-1); + if (!is_symlink_result && iter->is_directory(ec)) { + count += remove_all(iter->path(), ec); + if (ec) { + return static_cast(-1); + } + } + else { + if (!ec) { + remove(iter->path(), ec); + } + if (ec) { + return static_cast(-1); + } + ++count; + } + } + } + if (!ec) { + if (remove(p, ec)) { + ++count; + } + } + if (ec) { + return static_cast(-1); + } + return count; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void rename(const path& from, const path& to) +{ + std::error_code ec; + rename(from, to, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); + } +} +#endif + +GHC_INLINE void rename(const path& from, const path& to, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (from != to) { + if (!MoveFileExW(GHC_NATIVEWP(from), GHC_NATIVEWP(to), (DWORD)MOVEFILE_REPLACE_EXISTING)) { + ec = detail::make_system_error(); + } + } +#else + if (from != to) { + if (::rename(from.c_str(), to.c_str()) != 0) { + ec = detail::make_system_error(); + } + } +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void resize_file(const path& p, uintmax_t size) +{ + std::error_code ec; + resize_file(p, size, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void resize_file(const path& p, uintmax_t size, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + LARGE_INTEGER lisize; + lisize.QuadPart = static_cast(size); + if (lisize.QuadPart < 0) { +#ifdef ERROR_FILE_TOO_LARGE + ec = detail::make_system_error(ERROR_FILE_TOO_LARGE); +#else + ec = detail::make_system_error(223); +#endif + return; + } + detail::unique_handle file(CreateFileW(GHC_NATIVEWP(p), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL)); + if (!file) { + ec = detail::make_system_error(); + } + else if (SetFilePointerEx(file.get(), lisize, NULL, FILE_BEGIN) == 0 || SetEndOfFile(file.get()) == 0) { + ec = detail::make_system_error(); + } +#else + if (::truncate(p.c_str(), static_cast(size)) != 0) { + ec = detail::make_system_error(); + } +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE space_info space(const path& p) +{ + std::error_code ec; + auto result = space(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE space_info space(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + ULARGE_INTEGER freeBytesAvailableToCaller = {{ 0, 0 }}; + ULARGE_INTEGER totalNumberOfBytes = {{ 0, 0 }}; + ULARGE_INTEGER totalNumberOfFreeBytes = {{ 0, 0 }}; + if (!GetDiskFreeSpaceExW(GHC_NATIVEWP(p), &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes)) { + ec = detail::make_system_error(); + return {static_cast(-1), static_cast(-1), static_cast(-1)}; + } + return {static_cast(totalNumberOfBytes.QuadPart), static_cast(totalNumberOfFreeBytes.QuadPart), static_cast(freeBytesAvailableToCaller.QuadPart)}; +#else + struct ::statvfs sfs; + if (::statvfs(p.c_str(), &sfs) != 0) { + ec = detail::make_system_error(); + return {static_cast(-1), static_cast(-1), static_cast(-1)}; + } + return {static_cast(sfs.f_blocks) * static_cast(sfs.f_frsize), static_cast(sfs.f_bfree) * static_cast(sfs.f_frsize), static_cast(sfs.f_bavail) * static_cast(sfs.f_frsize)}; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status status(const path& p) +{ + std::error_code ec; + auto result = status(p, ec); + if (result.type() == file_type::none) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE file_status status(const path& p, std::error_code& ec) noexcept +{ + return detail::status_ex(p, ec); +} + +GHC_INLINE bool status_known(file_status s) noexcept +{ + return s.type() != file_type::none; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status symlink_status(const path& p) +{ + std::error_code ec; + auto result = symlink_status(p, ec); + if (result.type() == file_type::none) { + throw filesystem_error(detail::systemErrorText(ec.value()), ec); + } + return result; +} +#endif + +GHC_INLINE file_status symlink_status(const path& p, std::error_code& ec) noexcept +{ + return detail::symlink_status_ex(p, ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path temp_directory_path() +{ + std::error_code ec; + path result = temp_directory_path(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), ec); + } + return result; +} +#endif + +GHC_INLINE path temp_directory_path(std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + wchar_t buffer[512]; + auto rc = GetTempPathW(511, buffer); + if (!rc || rc > 511) { + ec = detail::make_system_error(); + return path(); + } + return path(std::wstring(buffer)); +#else + static const char* temp_vars[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR", nullptr}; + const char* temp_path = nullptr; + for (auto temp_name = temp_vars; *temp_name != nullptr; ++temp_name) { + temp_path = std::getenv(*temp_name); + if (temp_path) { + return path(temp_path); + } + } + return path("/tmp"); +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path weakly_canonical(const path& p) +{ + std::error_code ec; + auto result = weakly_canonical(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path weakly_canonical(const path& p, std::error_code& ec) noexcept +{ + path result; + ec.clear(); + bool scan = true; + for (auto pe : p) { + if (scan) { + std::error_code tec; + if (exists(result / pe, tec)) { + result /= pe; + } + else { + if (ec) { + return path(); + } + scan = false; + if (!result.empty()) { + result = canonical(result, ec) / pe; + if (ec) { + break; + } + } + else { + result /= pe; + } + } + } + else { + result /= pe; + } + } + if (scan) { + if (!result.empty()) { + result = canonical(result, ec); + } + } + return ec ? path() : result.lexically_normal(); +} + +//----------------------------------------------------------------------------- +// [fs.class.file_status] class file_status +// [fs.file_status.cons] constructors and destructor +GHC_INLINE file_status::file_status() noexcept + : file_status(file_type::none) +{ +} + +GHC_INLINE file_status::file_status(file_type ft, perms prms) noexcept + : _type(ft) + , _perms(prms) +{ +} + +GHC_INLINE file_status::file_status(const file_status& other) noexcept + : _type(other._type) + , _perms(other._perms) +{ +} + +GHC_INLINE file_status::file_status(file_status&& other) noexcept + : _type(other._type) + , _perms(other._perms) +{ +} + +GHC_INLINE file_status::~file_status() {} + +// assignments: +GHC_INLINE file_status& file_status::operator=(const file_status& rhs) noexcept +{ + _type = rhs._type; + _perms = rhs._perms; + return *this; +} + +GHC_INLINE file_status& file_status::operator=(file_status&& rhs) noexcept +{ + _type = rhs._type; + _perms = rhs._perms; + return *this; +} + +// [fs.file_status.mods] modifiers +GHC_INLINE void file_status::type(file_type ft) noexcept +{ + _type = ft; +} + +GHC_INLINE void file_status::permissions(perms prms) noexcept +{ + _perms = prms; +} + +// [fs.file_status.obs] observers +GHC_INLINE file_type file_status::type() const noexcept +{ + return _type; +} + +GHC_INLINE perms file_status::permissions() const noexcept +{ + return _perms; +} + +//----------------------------------------------------------------------------- +// [fs.class.directory_entry] class directory_entry +// [fs.dir.entry.cons] constructors and destructor +// directory_entry::directory_entry() noexcept = default; +// directory_entry::directory_entry(const directory_entry&) = default; +// directory_entry::directory_entry(directory_entry&&) noexcept = default; +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE directory_entry::directory_entry(const filesystem::path& p) + : _path(p) + , _file_size(static_cast(-1)) +#ifndef GHC_OS_WINDOWS + , _hard_link_count(static_cast(-1)) +#endif + , _last_write_time(0) +{ + refresh(); +} +#endif + +GHC_INLINE directory_entry::directory_entry(const filesystem::path& p, std::error_code& ec) + : _path(p) + , _file_size(static_cast(-1)) +#ifndef GHC_OS_WINDOWS + , _hard_link_count(static_cast(-1)) +#endif + , _last_write_time(0) +{ + refresh(ec); +} + +GHC_INLINE directory_entry::~directory_entry() {} + +// assignments: +// directory_entry& directory_entry::operator=(const directory_entry&) = default; +// directory_entry& directory_entry::operator=(directory_entry&&) noexcept = default; + +// [fs.dir.entry.mods] directory_entry modifiers +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void directory_entry::assign(const filesystem::path& p) +{ + _path = p; + refresh(); +} +#endif + +GHC_INLINE void directory_entry::assign(const filesystem::path& p, std::error_code& ec) +{ + _path = p; + refresh(ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void directory_entry::replace_filename(const filesystem::path& p) +{ + _path.replace_filename(p); + refresh(); +} +#endif + +GHC_INLINE void directory_entry::replace_filename(const filesystem::path& p, std::error_code& ec) +{ + _path.replace_filename(p); + refresh(ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void directory_entry::refresh() +{ + std::error_code ec; + refresh(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _path, ec); + } +} +#endif + +GHC_INLINE void directory_entry::refresh(std::error_code& ec) noexcept +{ +#ifdef GHC_OS_WINDOWS + _status = detail::status_ex(_path, ec, &_symlink_status, &_file_size, nullptr, &_last_write_time); +#else + _status = detail::status_ex(_path, ec, &_symlink_status, &_file_size, &_hard_link_count, &_last_write_time); +#endif +} + +// [fs.dir.entry.obs] directory_entry observers +GHC_INLINE const filesystem::path& directory_entry::path() const noexcept +{ + return _path; +} + +GHC_INLINE directory_entry::operator const filesystem::path&() const noexcept +{ + return _path; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_type directory_entry::status_file_type() const +{ + return _status.type() != file_type::none ? _status.type() : filesystem::status(path()).type(); +} +#endif + +GHC_INLINE file_type directory_entry::status_file_type(std::error_code& ec) const noexcept +{ + if (_status.type() != file_type::none) { + ec.clear(); + return _status.type(); + } + return filesystem::status(path(), ec).type(); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::exists() const +{ + return status_file_type() != file_type::not_found; +} +#endif + +GHC_INLINE bool directory_entry::exists(std::error_code& ec) const noexcept +{ + return status_file_type(ec) != file_type::not_found; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_block_file() const +{ + return status_file_type() == file_type::block; +} +#endif +GHC_INLINE bool directory_entry::is_block_file(std::error_code& ec) const noexcept +{ + return status_file_type(ec) == file_type::block; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_character_file() const +{ + return status_file_type() == file_type::character; +} +#endif + +GHC_INLINE bool directory_entry::is_character_file(std::error_code& ec) const noexcept +{ + return status_file_type(ec) == file_type::character; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_directory() const +{ + return status_file_type() == file_type::directory; +} +#endif + +GHC_INLINE bool directory_entry::is_directory(std::error_code& ec) const noexcept +{ + return status_file_type(ec) == file_type::directory; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_fifo() const +{ + return status_file_type() == file_type::fifo; +} +#endif + +GHC_INLINE bool directory_entry::is_fifo(std::error_code& ec) const noexcept +{ + return status_file_type(ec) == file_type::fifo; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_other() const +{ + auto ft = status_file_type(); + return ft != file_type::none && ft != file_type::not_found && ft != file_type::regular && ft != file_type::directory && !is_symlink(); +} +#endif + +GHC_INLINE bool directory_entry::is_other(std::error_code& ec) const noexcept +{ + auto ft = status_file_type(ec); + bool other = ft != file_type::none && ft != file_type::not_found && ft != file_type::regular && ft != file_type::directory && !is_symlink(ec); + return !ec && other; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_regular_file() const +{ + return status_file_type() == file_type::regular; +} +#endif + +GHC_INLINE bool directory_entry::is_regular_file(std::error_code& ec) const noexcept +{ + return status_file_type(ec) == file_type::regular; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_socket() const +{ + return status_file_type() == file_type::socket; +} +#endif + +GHC_INLINE bool directory_entry::is_socket(std::error_code& ec) const noexcept +{ + return status_file_type(ec) == file_type::socket; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_symlink() const +{ + return _symlink_status.type() != file_type::none ? _symlink_status.type() == file_type::symlink : filesystem::is_symlink(symlink_status()); +} +#endif + +GHC_INLINE bool directory_entry::is_symlink(std::error_code& ec) const noexcept +{ + if (_symlink_status.type() != file_type::none) { + ec.clear(); + return _symlink_status.type() == file_type::symlink; + } + return filesystem::is_symlink(symlink_status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t directory_entry::file_size() const +{ + if (_file_size != static_cast(-1)) { + return _file_size; + } + return filesystem::file_size(path()); +} +#endif + +GHC_INLINE uintmax_t directory_entry::file_size(std::error_code& ec) const noexcept +{ + if (_file_size != static_cast(-1)) { + ec.clear(); + return _file_size; + } + return filesystem::file_size(path(), ec); +} + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t directory_entry::hard_link_count() const +{ +#ifndef GHC_OS_WINDOWS + if (_hard_link_count != static_cast(-1)) { + return _hard_link_count; + } +#endif + return filesystem::hard_link_count(path()); +} +#endif + +GHC_INLINE uintmax_t directory_entry::hard_link_count(std::error_code& ec) const noexcept +{ +#ifndef GHC_OS_WINDOWS + if (_hard_link_count != static_cast(-1)) { + ec.clear(); + return _hard_link_count; + } +#endif + return filesystem::hard_link_count(path(), ec); +} +#endif + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_time_type directory_entry::last_write_time() const +{ + if (_last_write_time != 0) { + return std::chrono::system_clock::from_time_t(_last_write_time); + } + return filesystem::last_write_time(path()); +} +#endif + +GHC_INLINE file_time_type directory_entry::last_write_time(std::error_code& ec) const noexcept +{ + if (_last_write_time != 0) { + ec.clear(); + return std::chrono::system_clock::from_time_t(_last_write_time); + } + return filesystem::last_write_time(path(), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status directory_entry::status() const +{ + if (_status.type() != file_type::none && _status.permissions() != perms::unknown) { + return _status; + } + return filesystem::status(path()); +} +#endif + +GHC_INLINE file_status directory_entry::status(std::error_code& ec) const noexcept +{ + if (_status.type() != file_type::none && _status.permissions() != perms::unknown) { + ec.clear(); + return _status; + } + return filesystem::status(path(), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status directory_entry::symlink_status() const +{ + if (_symlink_status.type() != file_type::none && _symlink_status.permissions() != perms::unknown) { + return _symlink_status; + } + return filesystem::symlink_status(path()); +} +#endif + +GHC_INLINE file_status directory_entry::symlink_status(std::error_code& ec) const noexcept +{ + if (_symlink_status.type() != file_type::none && _symlink_status.permissions() != perms::unknown) { + ec.clear(); + return _symlink_status; + } + return filesystem::symlink_status(path(), ec); +} + +#ifdef GHC_HAS_THREEWAY_COMP +GHC_INLINE std::strong_ordering directory_entry::operator<=>(const directory_entry& rhs) const noexcept +{ + return _path <=> rhs._path; +} +#endif + +GHC_INLINE bool directory_entry::operator<(const directory_entry& rhs) const noexcept +{ + return _path < rhs._path; +} + +GHC_INLINE bool directory_entry::operator==(const directory_entry& rhs) const noexcept +{ + return _path == rhs._path; +} + +GHC_INLINE bool directory_entry::operator!=(const directory_entry& rhs) const noexcept +{ + return _path != rhs._path; +} + +GHC_INLINE bool directory_entry::operator<=(const directory_entry& rhs) const noexcept +{ + return _path <= rhs._path; +} + +GHC_INLINE bool directory_entry::operator>(const directory_entry& rhs) const noexcept +{ + return _path > rhs._path; +} + +GHC_INLINE bool directory_entry::operator>=(const directory_entry& rhs) const noexcept +{ + return _path >= rhs._path; +} + +//----------------------------------------------------------------------------- +// [fs.class.directory_iterator] class directory_iterator + +#ifdef GHC_OS_WINDOWS +class directory_iterator::impl +{ +public: + impl(const path& p, directory_options options) + : _base(p) + , _options(options) + , _dirHandle(INVALID_HANDLE_VALUE) + { + if (!_base.empty()) { + ZeroMemory(&_findData, sizeof(WIN32_FIND_DATAW)); + if ((_dirHandle = FindFirstFileW(GHC_NATIVEWP((_base / "*")), &_findData)) != INVALID_HANDLE_VALUE) { + if (std::wstring(_findData.cFileName) == L"." || std::wstring(_findData.cFileName) == L"..") { + increment(_ec); + } + else { + _dir_entry._path = _base / std::wstring(_findData.cFileName); + copyToDirEntry(_ec); + } + } + else { + auto error = ::GetLastError(); + _base = filesystem::path(); + if (error != ERROR_ACCESS_DENIED || (options & directory_options::skip_permission_denied) == directory_options::none) { + _ec = detail::make_system_error(); + } + } + } + } + impl(const impl& other) = delete; + ~impl() + { + if (_dirHandle != INVALID_HANDLE_VALUE) { + FindClose(_dirHandle); + _dirHandle = INVALID_HANDLE_VALUE; + } + } + void increment(std::error_code& ec) + { + if (_dirHandle != INVALID_HANDLE_VALUE) { + do { + if (FindNextFileW(_dirHandle, &_findData)) { + _dir_entry._path = _base; +#ifdef GHC_USE_WCHAR_T + _dir_entry._path.append_name(_findData.cFileName); +#else +#ifdef GHC_RAISE_UNICODE_ERRORS + try { + _dir_entry._path.append_name(detail::toUtf8(_findData.cFileName).c_str()); + } + catch (filesystem_error& fe) { + ec = fe.code(); + return; + } +#else + _dir_entry._path.append_name(detail::toUtf8(_findData.cFileName).c_str()); +#endif +#endif + copyToDirEntry(ec); + } + else { + auto err = ::GetLastError(); + if (err != ERROR_NO_MORE_FILES) { + _ec = ec = detail::make_system_error(err); + } + FindClose(_dirHandle); + _dirHandle = INVALID_HANDLE_VALUE; + _dir_entry._path.clear(); + break; + } + } while (std::wstring(_findData.cFileName) == L"." || std::wstring(_findData.cFileName) == L".."); + } + else { + ec = _ec; + } + } + void copyToDirEntry(std::error_code& ec) + { + if (_findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + _dir_entry._status = detail::status_ex(_dir_entry._path, ec, &_dir_entry._symlink_status, &_dir_entry._file_size, nullptr, &_dir_entry._last_write_time); + } + else { + _dir_entry._status = detail::status_from_INFO(_dir_entry._path, &_findData, ec, &_dir_entry._file_size, &_dir_entry._last_write_time); + _dir_entry._symlink_status = _dir_entry._status; + } + if (ec) { + if (_dir_entry._status.type() != file_type::none && _dir_entry._symlink_status.type() != file_type::none) { + ec.clear(); + } + else { + _dir_entry._file_size = static_cast(-1); + _dir_entry._last_write_time = 0; + } + } + } + path _base; + directory_options _options; + WIN32_FIND_DATAW _findData; + HANDLE _dirHandle; + directory_entry _dir_entry; + std::error_code _ec; +}; +#else +// POSIX implementation +class directory_iterator::impl +{ +public: + impl(const path& path, directory_options options) + : _base(path) + , _options(options) + , _dir(nullptr) + , _entry(nullptr) + { + if (!path.empty()) { + _dir = ::opendir(path.native().c_str()); + if (!_dir) { + auto error = errno; + _base = filesystem::path(); + if ((error != EACCES && error != EPERM) || (options & directory_options::skip_permission_denied) == directory_options::none) { + _ec = detail::make_system_error(); + } + } + else { + increment(_ec); + } + } + } + impl(const impl& other) = delete; + ~impl() + { + if (_dir) { + ::closedir(_dir); + } + } + void increment(std::error_code& ec) + { + if (_dir) { + bool skip; + do { + skip = false; + errno = 0; + _entry = ::readdir(_dir); + if (_entry) { + _dir_entry._path = _base; + _dir_entry._path.append_name(_entry->d_name); + copyToDirEntry(); + if (ec && (ec.value() == EACCES || ec.value() == EPERM) && (_options & directory_options::skip_permission_denied) == directory_options::skip_permission_denied) { + ec.clear(); + skip = true; + } + } + else { + ::closedir(_dir); + _dir = nullptr; + _dir_entry._path.clear(); + if (errno) { + ec = detail::make_system_error(); + } + break; + } + } while (skip || std::strcmp(_entry->d_name, ".") == 0 || std::strcmp(_entry->d_name, "..") == 0); + } + } + + void copyToDirEntry() + { + _dir_entry._symlink_status.permissions(perms::unknown); + auto ft = detail::file_type_from_dirent(*_entry); + _dir_entry._symlink_status.type(ft); + if (ft != file_type::symlink) { + _dir_entry._status = _dir_entry._symlink_status; + } + else { + _dir_entry._status.type(file_type::none); + _dir_entry._status.permissions(perms::unknown); + } + _dir_entry._file_size = static_cast(-1); + _dir_entry._hard_link_count = static_cast(-1); + _dir_entry._last_write_time = 0; + } + path _base; + directory_options _options; + DIR* _dir; + struct ::dirent* _entry; + directory_entry _dir_entry; + std::error_code _ec; +}; +#endif + +// [fs.dir.itr.members] member functions +GHC_INLINE directory_iterator::directory_iterator() noexcept + : _impl(new impl(path(), directory_options::none)) +{ +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE directory_iterator::directory_iterator(const path& p) + : _impl(new impl(p, directory_options::none)) +{ + if (_impl->_ec) { + throw filesystem_error(detail::systemErrorText(_impl->_ec.value()), p, _impl->_ec); + } + _impl->_ec.clear(); +} + +GHC_INLINE directory_iterator::directory_iterator(const path& p, directory_options options) + : _impl(new impl(p, options)) +{ + if (_impl->_ec) { + throw filesystem_error(detail::systemErrorText(_impl->_ec.value()), p, _impl->_ec); + } +} +#endif + +GHC_INLINE directory_iterator::directory_iterator(const path& p, std::error_code& ec) noexcept + : _impl(new impl(p, directory_options::none)) +{ + if (_impl->_ec) { + ec = _impl->_ec; + } +} + +GHC_INLINE directory_iterator::directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept + : _impl(new impl(p, options)) +{ + if (_impl->_ec) { + ec = _impl->_ec; + } +} + +GHC_INLINE directory_iterator::directory_iterator(const directory_iterator& rhs) + : _impl(rhs._impl) +{ +} + +GHC_INLINE directory_iterator::directory_iterator(directory_iterator&& rhs) noexcept + : _impl(std::move(rhs._impl)) +{ +} + +GHC_INLINE directory_iterator::~directory_iterator() {} + +GHC_INLINE directory_iterator& directory_iterator::operator=(const directory_iterator& rhs) +{ + _impl = rhs._impl; + return *this; +} + +GHC_INLINE directory_iterator& directory_iterator::operator=(directory_iterator&& rhs) noexcept +{ + _impl = std::move(rhs._impl); + return *this; +} + +GHC_INLINE const directory_entry& directory_iterator::operator*() const +{ + return _impl->_dir_entry; +} + +GHC_INLINE const directory_entry* directory_iterator::operator->() const +{ + return &_impl->_dir_entry; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE directory_iterator& directory_iterator::operator++() +{ + std::error_code ec; + _impl->increment(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_entry._path, ec); + } + return *this; +} +#endif + +GHC_INLINE directory_iterator& directory_iterator::increment(std::error_code& ec) noexcept +{ + _impl->increment(ec); + return *this; +} + +GHC_INLINE bool directory_iterator::operator==(const directory_iterator& rhs) const +{ + return _impl->_dir_entry._path == rhs._impl->_dir_entry._path; +} + +GHC_INLINE bool directory_iterator::operator!=(const directory_iterator& rhs) const +{ + return _impl->_dir_entry._path != rhs._impl->_dir_entry._path; +} + +// [fs.dir.itr.nonmembers] directory_iterator non-member functions + +GHC_INLINE directory_iterator begin(directory_iterator iter) noexcept +{ + return iter; +} + +GHC_INLINE directory_iterator end(const directory_iterator&) noexcept +{ + return directory_iterator(); +} + +//----------------------------------------------------------------------------- +// [fs.class.rec.dir.itr] class recursive_directory_iterator + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator() noexcept + : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator()); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p) + : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p)); +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options) + : _impl(new recursive_directory_iterator_impl(options, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p, options)); +} +#endif + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept + : _impl(new recursive_directory_iterator_impl(options, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p, options, ec)); +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, std::error_code& ec) noexcept + : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p, ec)); +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const recursive_directory_iterator& rhs) + : _impl(rhs._impl) +{ +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(recursive_directory_iterator&& rhs) noexcept + : _impl(std::move(rhs._impl)) +{ +} + +GHC_INLINE recursive_directory_iterator::~recursive_directory_iterator() {} + +// [fs.rec.dir.itr.members] observers +GHC_INLINE directory_options recursive_directory_iterator::options() const +{ + return _impl->_options; +} + +GHC_INLINE int recursive_directory_iterator::depth() const +{ + return static_cast(_impl->_dir_iter_stack.size() - 1); +} + +GHC_INLINE bool recursive_directory_iterator::recursion_pending() const +{ + return _impl->_recursion_pending; +} + +GHC_INLINE const directory_entry& recursive_directory_iterator::operator*() const +{ + return *(_impl->_dir_iter_stack.top()); +} + +GHC_INLINE const directory_entry* recursive_directory_iterator::operator->() const +{ + return &(*(_impl->_dir_iter_stack.top())); +} + +// [fs.rec.dir.itr.members] modifiers recursive_directory_iterator& +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator=(const recursive_directory_iterator& rhs) +{ + _impl = rhs._impl; + return *this; +} + +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator=(recursive_directory_iterator&& rhs) noexcept +{ + _impl = std::move(rhs._impl); + return *this; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator++() +{ + std::error_code ec; + increment(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_iter_stack.empty() ? path() : _impl->_dir_iter_stack.top()->path(), ec); + } + return *this; +} +#endif + +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::increment(std::error_code& ec) noexcept +{ + bool isSymLink = (*this)->is_symlink(ec); + bool isDir = !ec && (*this)->is_directory(ec); + if (isSymLink && detail::is_not_found_error(ec)) { + ec.clear(); + } + if (!ec) { + if (recursion_pending() && isDir && (!isSymLink || (options() & directory_options::follow_directory_symlink) != directory_options::none)) { + _impl->_dir_iter_stack.push(directory_iterator((*this)->path(), _impl->_options, ec)); + } + else { + _impl->_dir_iter_stack.top().increment(ec); + } + if (!ec) { + while (depth() && _impl->_dir_iter_stack.top() == directory_iterator()) { + _impl->_dir_iter_stack.pop(); + _impl->_dir_iter_stack.top().increment(ec); + } + } + else if (!_impl->_dir_iter_stack.empty()) { + _impl->_dir_iter_stack.pop(); + } + _impl->_recursion_pending = true; + } + return *this; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void recursive_directory_iterator::pop() +{ + std::error_code ec; + pop(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_iter_stack.empty() ? path() : _impl->_dir_iter_stack.top()->path(), ec); + } +} +#endif + +GHC_INLINE void recursive_directory_iterator::pop(std::error_code& ec) +{ + if (depth() == 0) { + *this = recursive_directory_iterator(); + } + else { + do { + _impl->_dir_iter_stack.pop(); + _impl->_dir_iter_stack.top().increment(ec); + } while (depth() && _impl->_dir_iter_stack.top() == directory_iterator()); + } +} + +GHC_INLINE void recursive_directory_iterator::disable_recursion_pending() +{ + _impl->_recursion_pending = false; +} + +// other members as required by [input.iterators] +GHC_INLINE bool recursive_directory_iterator::operator==(const recursive_directory_iterator& rhs) const +{ + return _impl->_dir_iter_stack.top() == rhs._impl->_dir_iter_stack.top(); +} + +GHC_INLINE bool recursive_directory_iterator::operator!=(const recursive_directory_iterator& rhs) const +{ + return _impl->_dir_iter_stack.top() != rhs._impl->_dir_iter_stack.top(); +} + +// [fs.rec.dir.itr.nonmembers] directory_iterator non-member functions +GHC_INLINE recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept +{ + return iter; +} + +GHC_INLINE recursive_directory_iterator end(const recursive_directory_iterator&) noexcept +{ + return recursive_directory_iterator(); +} + +#endif // GHC_EXPAND_IMPL + +} // namespace filesystem +} // namespace ghc + +// cleanup some macros +#undef GHC_INLINE +#undef GHC_EXPAND_IMPL + +#endif // GHC_FILESYSTEM_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_fwd.hpp b/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_fwd.hpp new file mode 100644 index 000000000..31188d162 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_fwd.hpp @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------------------------- +// +// ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14 +// +//--------------------------------------------------------------------------------------- +// +// Copyright (c) 2018, Steffen Schümann +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +//--------------------------------------------------------------------------------------- +// fs_fwd.hpp - The forwarding header for the header/implementation seperated usage of +// ghc::filesystem. +// This file can be include at any place, where ghc::filesystem api is needed while +// not bleeding implementation details (e.g. system includes) into the global namespace, +// as long as one cpp includes fs_impl.hpp to deliver the matching implementations. +//--------------------------------------------------------------------------------------- +#ifndef GHC_FILESYSTEM_FWD_H +#define GHC_FILESYSTEM_FWD_H +#define GHC_FILESYSTEM_FWD +#include +#endif // GHC_FILESYSTEM_FWD_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_impl.hpp b/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_impl.hpp new file mode 100644 index 000000000..92e3eaefe --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_impl.hpp @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------------------------- +// +// ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14 +// +//--------------------------------------------------------------------------------------- +// +// Copyright (c) 2018, Steffen Schümann +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +//--------------------------------------------------------------------------------------- +// fs_impl.hpp - The implementation header for the header/implementation seperated usage of +// ghc::filesystem. +// This file can be used to hide the implementation of ghc::filesystem into a single cpp. +// The cpp has to include this before including fs_fwd.hpp directly or via a different +// header to work. +//--------------------------------------------------------------------------------------- +#define GHC_FILESYSTEM_IMPLEMENTATION +#include diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_std.hpp b/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_std.hpp new file mode 100644 index 000000000..c9492fdcb --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_std.hpp @@ -0,0 +1,60 @@ +//--------------------------------------------------------------------------------------- +// +// ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14 +// +//--------------------------------------------------------------------------------------- +// +// Copyright (c) 2018, Steffen Schümann +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +//--------------------------------------------------------------------------------------- +// fs_std.hpp - The dynamic switching header that includes std::filesystem if detected +// or ghc::filesystem if not, and makes the resulting API available in the +// namespace fs. +//--------------------------------------------------------------------------------------- +#ifndef GHC_FILESYSTEM_STD_H +#define GHC_FILESYSTEM_STD_H +#if defined(__APPLE__) +#include +#endif +#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) +#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) +#define GHC_USE_STD_FS +#include +namespace fs { +using namespace std::filesystem; +using ifstream = std::ifstream; +using ofstream = std::ofstream; +using fstream = std::fstream; +} +#endif +#endif +#ifndef GHC_USE_STD_FS +//#define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE +#include +namespace fs { +using namespace ghc::filesystem; +using ifstream = ghc::filesystem::ifstream; +using ofstream = ghc::filesystem::ofstream; +using fstream = ghc::filesystem::fstream; +} +#endif +#endif // GHC_FILESYSTEM_STD_H + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_std_fwd.hpp b/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_std_fwd.hpp new file mode 100644 index 000000000..163c956ac --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_std_fwd.hpp @@ -0,0 +1,63 @@ +//--------------------------------------------------------------------------------------- +// +// ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14 +// +//--------------------------------------------------------------------------------------- +// +// Copyright (c) 2018, Steffen Schümann +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +//--------------------------------------------------------------------------------------- +// fs_std_fwd.hpp - The forwarding header for the header/implementation seperated usage of +// ghc::filesystem that uses std::filesystem if it detects it. +// This file can be include at any place, where fs::filesystem api is needed while +// not bleeding implementation details (e.g. system includes) into the global namespace, +// as long as one cpp includes fs_std_impl.hpp to deliver the matching implementations. +//--------------------------------------------------------------------------------------- +#ifndef GHC_FILESYSTEM_STD_FWD_H +#define GHC_FILESYSTEM_STD_FWD_H +#if defined(__APPLE__) +#include +#endif +#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) +#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) +#define GHC_USE_STD_FS +#include +namespace fs { +using namespace std::filesystem; +using ifstream = std::ifstream; +using ofstream = std::ofstream; +using fstream = std::fstream; +} +#endif +#endif +#ifndef GHC_USE_STD_FS +//#define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE +#define GHC_FILESYSTEM_FWD +#include +namespace fs { +using namespace ghc::filesystem; +using ifstream = ghc::filesystem::ifstream; +using ofstream = ghc::filesystem::ofstream; +using fstream = ghc::filesystem::fstream; +} +#endif +#endif // GHC_FILESYSTEM_STD_FWD_H + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_std_impl.hpp b/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_std_impl.hpp new file mode 100644 index 000000000..7042edca2 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/filesystem/include/ghc/fs_std_impl.hpp @@ -0,0 +1,46 @@ +//--------------------------------------------------------------------------------------- +// +// ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14 +// +//--------------------------------------------------------------------------------------- +// +// Copyright (c) 2018, Steffen Schümann +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +//--------------------------------------------------------------------------------------- +// fs_std_impl.hpp - The implementation header for the header/implementation seperated usage of +// ghc::filesystem that does nothing if std::filesystem is detected. +// This file can be used to hide the implementation of ghc::filesystem into a single cpp. +// The cpp has to include this before including fs_std_fwd.hpp directly or via a different +// header to work. +//--------------------------------------------------------------------------------------- +#if defined(__APPLE__) +#include +#endif +#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) +#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) +#define GHC_USE_STD_FS +#endif +#endif +#ifndef GHC_USE_STD_FS +//#define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE +#define GHC_FILESYSTEM_IMPLEMENTATION +#include +#endif diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/LICENSE b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/README.md b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/README.md new file mode 100644 index 000000000..b6ab14282 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/README.md @@ -0,0 +1,112 @@ +floaxie +======= +[![Build Status in GCC/Clang](https://travis-ci.org/aclex/floaxie.svg?branch=master)](https://travis-ci.org/aclex/floaxie) [![Build status in Visual Studio](https://ci.appveyor.com/api/projects/status/nhidn1n2o66etirk?svg=true)](https://ci.appveyor.com/project/aclex/floaxie) [![Code coverage](https://codecov.io/gh/aclex/floaxie/branch/master/graph/badge.svg)](https://codecov.io/gh/aclex/floaxie) + +Floaxie is C++14 header-only library for [fast](https://github.com/miloyip/dtoa-benchmark/) printing floating point values of arbitrary precision (`float`, `double` etc.) and parsing their textual representation back again (in ordinary or exponent notation). + +What is it for? +--------------- + +One is obviously not worried too much about the speed of the values being printed on the console, so the primary places to use the library are readers, writers, encoders and decoders of different text-based formats (e.g. JSON, XML and so on), applications interacting through text pipes or rendering data structures. + +Please, take a look also at the alternatives solving the same problem mentioned in the benchmark of @miloyip [here](https://github.com/miloyip/dtoa-benchmark/), if `floaxie` doesn't suite you well. + +Compiler compatibility +---------------------- +- [x] GCC 5 +- [x] Clang 3.6 +- [x] Visual Studio 2017 15.0 + +Printing +-------- +**Grisu2** algorithm is adopted for printing floating point values. It is fast printing algorithm described by Florian Loitsch in his [Printing Floating-Point Numbers Quickly and Accurately with Integers](http://florian.loitsch.com/publications/dtoa-pldi2010.pdf) paper. **Grisu2** is chosen as probably the fastest **grisu** version, for cases, where shortest possible representation in 100% of cases is not ultimately important. Still it guarantees the best possible efficiency of more, than 99%. + +Parsing +------- +The opposite to **Grisu** algorithm used for printing, an algorithm on the same theoretical base, but for parsing, is developed. Following the analogue of **Grisu** naming, who essentially appears to be cartoon character (Dragon), parsing algorithm is named after another animated character, rabbit, **Krosh:** ![Krosh](http://img4.wikia.nocookie.net/__cb20130427170555/smesharikiarhives/ru/images/0/03/%D0%9A%D1%80%D0%BE%D1%88.png "Krosh") + +The algorithm parses decimal mantissa to extent of slightly more decimal digit capacity of floating point types, chooses a pre-calculated decimal power and then multiplies the two. Since the [rounding problem](http://www.exploringbinary.com/decimal-to-floating-point-needs-arbitrary-precision/) is not uncommon during such operations, and, in contrast to printing problem, one can't just return incorrectly rounded parsing results, such cases are detected instead and slower, but accurate fallback conversion is performed (C Standard Library functions like `strtod()` by default). In this respect **Krosh** is closer to **Grisu3**. + +Example +------- +**Printing:** +```cpp +#include + +#include "floaxie/ftoa.h" + +using namespace std; +using namespace floaxie; + +int main(int, char**) +{ + double pi = 0.1; + char buffer[128]; + + ftoa(pi, buffer); + cout << "pi: " << pi << ", buffer: " << buffer << endl; + + return 0; +} +``` + +**Parsing:** +```cpp +#include + +#include "floaxie/atof.h" + +using namespace std; +using namespace floaxie; + +int main(int, char**) +{ + char str[] = "0.1"; + char* str_end; + double pi = 0; + + pi = atof(str, &str_end); + cout << "pi: " << pi << ", str: " << str << ", str_end: " << str_end << endl; + + return 0; +} +``` + +Building +-------- + +Building is not required and completely optional, unless you would like to try the examples or tests or build the local documentation. Inside `git` project tree it can be done like this: + +```shell +git submodule update --init # to check out common CMake modules' submodule +mkdir build && cd build +cmake -DBUILD_EXAMPLES=1 -DBUILD_TESTS=1 -DBUILD_DOCUMENTATION=1 ../ # switch on the desired flags +cmake --build . # or just `make` on systems with it +``` + +Adding to the project +--------------------- + +```shell +git submodule add https://github.com/aclex/floaxie +``` + +Don't forget to do `git submodule update --init --recursive` out of your project tree to pull submodules properly. + +Including to CMake project as a subproject +------- +Since version 1.2 it's possible to include Floaxie as a subproject in any CMake project quite easily thanks to modern CMake `INTERFACE_LIBRARY` target facilities. Unfortunately, this works fully since CMake 3.13, so its minimum required version had to be increased. + +`CMakeLists.txt` of a consumer CMake project would look like this (given Floaxie is cloned to `floaxie` subdirectory)': +```cmake +project(foo) + +cmake_minimum_required(VERSION 3.13) + +# `EXCLUDE_FOR_ALL` here to exclude supplementary targets +# like `install` from the main project target set +add_subdirectory(peli EXCLUDE_FROM_ALL) + +add_executable(foo_main foo_main.cpp) +target_link_libraries(foo_main PUBLIC floaxie) +``` diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/atof.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/atof.h new file mode 100644 index 000000000..418153651 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/atof.h @@ -0,0 +1,179 @@ +/* + * Copyright 2015-2019 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOAXIE_ATOF_H +#define FLOAXIE_ATOF_H + +#include + +#include + +#include + +#include + +/** \brief Floaxie functions templates. + * + * This namespace contains two main public floaxie functions (`atof()` and + * `ftoa()`), as well as several helper functions (e.g. `max_buffer_size()`) + * and internal type and function templates. + */ +namespace floaxie +{ + /** \brief Small decorator around returning value to help the client + * optionally receive minor error states along with it. + * + * \tparam FloatType target floating point type to store results. + */ + template struct value_and_status + { + /** \brief The returning result value itself. */ + FloatType value; + /** \brief Conversion status indicating any problems occurred. */ + conversion_status status; + + /** \brief Constructs the object with empty value and successful status. */ + value_and_status() noexcept : value(), status(conversion_status::success) { } + /** \brief Default conversion operator to `FloatType` to make use of the + * wrapper more transparent. */ + operator FloatType() const noexcept { return value; } + }; + + /** \brief Parses floating point string representation. + * + * Interprets string representation of floating point value using Krosh + * algorithm and, if successful, value of the specified type is returned. + * + * The accepted representation format is ordinary or exponential decimal + * floating point expression, containing: + * - optional sign ('+' or '-') + * - sequence of one or more decimal digits optionally containing decimal + * point character ('.') + * - optional 'e' of 'E' character followed by optional sign ('+' or '-') + * and sequence of one or more decimal digits. + * + * Function doesn't expect any preceding spacing characters and treats the + * representation as incorrect, if there's any. + * + * \tparam FloatType target floating point type to store results. + * \tparam CharType character type (typically `char` or `wchar_t`) the input + * string \p **str** consists of. + * \tparam FallbackCallable fallback conversion function type, in case of + * Krosh is unsure if the result is correctly rounded (default is `strtof()` + * for `float`'s, `strtod()` for `double`'s, `strtold()` for `long double`'s). + * + * \param str buffer containing the string representation of the value. + * \param str_end out parameter, which will contain a pointer to first + * character after the parsed value in the specified buffer. If str_end is + * null, it is ignored. + * \param fallback_func pointer to fallback function. If omitted, by default + * is `strtof()` for `float`'s, `strtod()` for `double`'s, `strtold()` for + * `long double`'s. Null value will lead to undefined behaviour in case of + * algorithm is unsure and fall back to using it. + * + * \return structure containing the parsed value, if the + * input is correct (default constructed value otherwise) and status of the + * conversion made. + * + * \sa `value_and_status` + * \sa `conversion_status` + */ + template + < + typename FloatType, + typename CharType, + typename FallbackCallable = FloatType (const CharType*, CharType**) + > + inline value_and_status atof(const CharType* str, CharType** str_end, FallbackCallable fallback_func = default_fallback) + { + value_and_status result; + + const auto& cr(krosh(str)); + + if (cr.str_end != str) + { + if (cr.is_accurate) + { + result.value = cr.value; + result.status = cr.status; + } + else + { + result.value = fallback_func(str, str_end); + result.status = check_errno(result.value); + + return result; + } + } + + if (str_end) + *str_end = const_cast(cr.str_end); + + return result; + } + + /** \brief Tiny overload for `atof()` function to allow passing `nullptr` + * as `str_end` parameter. + */ + template + < + typename FloatType, + typename CharType, + typename FallbackCallable = FloatType (const CharType*, CharType**) + > + inline value_and_status atof(const CharType* str, std::nullptr_t str_end, FallbackCallable fallback_func = default_fallback) + { + return atof(str, static_cast(str_end), fallback_func); + } + + /** \brief Parses floating point represented in `std::basic_string`. + * + * `atof()` adapter, which may be more useful for cases, where + * `std::basic_string` strings are widely used. + * + * \tparam FloatType target floating point type to store results. + * \tparam CharType character type (typically `char` or `wchar_t`) the input + * string \p **str** consists of. + * \tparam FallbackCallable fallback conversion function type, in case of + * Krosh is unsure if the result is correctly rounded (default is `strtof()` + * for `float`'s, `strtod()` for `double`'s, `strtold()` for `long double`'s). + * + * \param str string representation of the value. + * \param fallback_func pointer to fallback function. If omitted, by default + * is `strtof()` for `float`'s, `strtod()` for `double`'s, `strtold()` for + * `long double`'s. Null value will lead to undefined behaviour in case of + * algorithm is unsure and fall back to using it. + * + * \return structure containing the parsed value, if the + * input is correct (default constructed value otherwise) and status of the + * conversion made. + * + * \sa `value_and_status` + * \sa `conversion_status` + */ + template + < + typename FloatType, + typename CharType, + typename FallbackCallable = FloatType (const CharType*, CharType**) + > + inline value_and_status from_string(const std::basic_string& str, FallbackCallable fallback_func = default_fallback) + { + return atof(str.c_str(), nullptr, fallback_func); + } +} + +#endif // FLOAXIE_ATOF_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/bit_ops.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/bit_ops.h new file mode 100644 index 000000000..292fd32e6 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/bit_ops.h @@ -0,0 +1,220 @@ +/* + * Copyright 2015-2019 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * diy_fp class and helper functions use code and influenced by + * Florian Loitsch's original Grisu algorithms implementation + * (http://florian.loitsch.com/publications/bench.tar.gz) + * and "Printing Floating-Point Numbers Quickly and Accurately with + * Integers" paper + * (http://florian.loitsch.com/publications/dtoa-pldi2010.pdf) + */ + +#ifndef FLOAXIE_BIT_OPS_H +#define FLOAXIE_BIT_OPS_H + +#include +#include +#include +#include + +#include + +namespace floaxie +{ + /** \brief Calculates size of type in bits in compile time. + * + * \tparam NumericType type to calculate size in bits of. + * + * \return type size in bits. + */ + template constexpr std::size_t bit_size() noexcept + { + return sizeof(NumericType) * std::numeric_limits::digits; + } + + /** \brief Returns a value with bit of the specified power raised. + * + * Calculates a value, which equals to 2 in the specified power, i.e. with + * bit at \p `power` position is `1` and all the remaining bits are `0`. + * + * \tparam NumericType type to store the calculated value. + * + * \param power power (0-based index, counting right to left) of bit to + * raise. + * + * \return value of \p **NumericType** with \p **power**-th bit is `1` and + * all the remaining bits are `0`. + */ + template constexpr NumericType raised_bit(std::size_t power) + { + assert(power < bit_size()); + return NumericType(1) << power; + } + + /** \brief Returns Most Significant Bit (MSB) value for the specified type. + * + * Calculates a value, which is equal to the value of Most + * Significant Bit of the integer type, which has the same length, as the + * specified one. The left most bit of the calculated value is equal to + * `1`, and the remaining bits are `0`. + * + * \tparam FloatType type to calculate MSB value for. + * \tparam NumericType integer type of the same size, as \p **FloatType**. + * + * \return value of Most Significant Bit (MSB). + */ + template::type> + constexpr NumericType msb_value() noexcept + { + return raised_bit(bit_size() - 1); + } + + /** \brief Returns maximum unsigned integer value for the specified type. + * + * Calculates maximum value (using `std::numeric_limits`) of the integer + * type, which has the same length, as the specified one. Thus, all bits + * of the calculated value are equal to `1`. + * + * \tparam FloatType type to calculate MSB value for. + * \tparam NumericType integer type of the same size, as \p **FloatType**. + * + * \return maximum value of size the same as of the specified type. + */ + template::type> + constexpr NumericType max_integer_value() noexcept + { + return std::numeric_limits::max(); + } + + /** \brief Masks `n`-th bit of the specified value. + * + * Calculates a mask standing for the `n`-th bit, performs bitwise **AND** + * operation and returns the value of it. + * + * \param value the value, of which the specified bit is returned. + * \param power power (0-based right-to-left index) of bit to return. + * + * \return integer value, which has \p `power`-th bit of the \p **value** + * and the remaining bits equal to `0`. + * + */ + template constexpr bool nth_bit(NumericType value, std::size_t power) noexcept + { + return value & raised_bit(power); + } + + /** \brief Returns Most Significant Bit (MSB) of the specified value. + * + * Masks the left most bit of the given value, performs bitwise **AND** + * operation with the mask and the value and returns the result. + * + * \tparam NumericType type of the value. + * + * \param value value to get the highest bit of. + * + * \return integer value, which left most bit of the \p **value** and the + * remaining bits equal to `0`. + */ + template constexpr bool highest_bit(NumericType value) noexcept + { + return nth_bit(value, bit_size() - 1); + } + + /** \brief Returns mask of \p **n** bits from the right. + * + * \tparam NumericType type of the returned value. + * \param n number of bits from the right to mask. + * + * \return integer value with \p **n** right bits equal to `1` and the + * remaining bits equal to `0`. + */ + template constexpr NumericType mask(std::size_t n) noexcept + { + static_assert(!std::is_signed::value, "Unsigned integral type is expected."); + return n < bit_size() ? raised_bit(n) - 1 : std::numeric_limits::max(); + } + + /** \brief Rectified linear function. + * + * Returns the argument (\p **value**), if it's positive and `0` otherwise. + * + * \param value the argument. + * + * \return \p **value**, if \p **value** > `0`, `0` otherwise. + * + */ + template constexpr typename std::make_unsigned::type positive_part(NumericType value) noexcept + { + return static_cast::type>((std::max)(0, value)); + } + + /** \brief Return structure for `round_up` function. */ + struct round_result + { + /** \brief Round up result — flag indicating if the value should be + *rounded up (i.e. incremented). + */ + bool value; + /** \brief Flag indicating if the rounding was accurate. */ + bool is_accurate; + }; + + /** \brief Detects if rounding up should be done. + * + * Applies IEEE algorithm of rounding up detection. The rounding criteria + * requires, that rounding bit (the bit to the right of target position, + * which rounding is being performed to) equals to `1`, and one of the + * following conditions is true: * - at least one bit to the right of the rounding bit equals to `1` + * - the bit in the target position equals to `1` + * + * \tparam NumericType type of \p **last_bits** parameter (auto-calculated). + * + * \param last_bits right suffix of the value, where rounding takes place. + * \param round_to_power the power (0-based right-to-left index) of the + * target position (which rounding is being performed to). According to the + * algorithm math it should be greater, than zero, otherwise behaviour is + * undefined. + * + * \returns `round_result` structure with the rounding decision. + */ + template inline round_result round_up(NumericType last_bits, std::size_t round_to_power) noexcept + { + round_result ret; + + assert(round_to_power > 0); + + const NumericType round_bit(raised_bit(round_to_power - 1)); + const NumericType check_mask(mask(round_to_power + 1) ^ round_bit); + ret.is_accurate = (last_bits & mask(round_to_power)) != round_bit; + ret.value = (last_bits & round_bit) && (last_bits & check_mask); + + return ret; + } + + /** \brief `constexpr` version of `std::abs`, as the latter lacks `constepxr`. + * + * And is really not `constexpr` in e.g. Clang. + * + * \returns absolute value of the specified value. + */ + template constexpr NumericType constexpr_abs(NumericType value) + { + return NumericType(0) < value ? value : -value; + } +} + +#endif // FLOAXIE_BIT_OPS_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/cached_power.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/cached_power.h new file mode 100644 index 000000000..6c7bb11b6 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/cached_power.h @@ -0,0 +1,53 @@ +/* + * Copyright 2015-2017 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * cached power literals and cached_power() function use code taken from + * Florian Loitsch's original Grisu algorithms implementation + * (http://florian.loitsch.com/publications/bench.tar.gz) + * and "Printing Floating-Point Numbers Quickly and Accurately with + * Integers" paper + * (http://florian.loitsch.com/publications/dtoa-pldi2010.pdf) + */ + +#ifndef FLOAXIE_CACHED_POWER_H +#define FLOAXIE_CACHED_POWER_H + +#include +#include + +#include +#include + +#include + +namespace floaxie +{ + /** \brief Returns pre-calculated `diy_fp` value of 10 in the specified + * power using pre-calculated and compiled version of binary mantissa + * and exponent. + * + * \tparam FloatType floating point type to call the values for. + */ + template inline diy_fp cached_power(int k) noexcept + { + assert(k >= -static_cast(powers_ten::pow_0_offset)); + + const std::size_t index = powers_ten::pow_0_offset + k; + + return diy_fp(powers_ten::f[index], powers_ten::e[index]); + } +} + +#endif // FLOAXIE_CACHED_POWER_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/conversion_status.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/conversion_status.h new file mode 100644 index 000000000..590ab876a --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/conversion_status.h @@ -0,0 +1,34 @@ +/* + * Copyright 2015, 2016, 2017 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOAXIE_CONVERSION_STATUS_H +#define FLOAXIE_CONVERSION_STATUS_H + +namespace floaxie +{ + /** \brief Enumeration of possible conversion results, either successful or + * not. + */ + enum class conversion_status : unsigned char + { + + success, /**< The conversion was successful. */ + underflow, /**< An underflow occurred during the conversion. */ + overflow /**< An overflow occurred during the conversion. */ + }; +} + +#endif // FLOAXIE_CONVERSION_STATUS_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/default_fallback.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/default_fallback.h new file mode 100644 index 000000000..bd305efd1 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/default_fallback.h @@ -0,0 +1,94 @@ +/* + * Copyright 2015, 2016 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOAXIE_DEFAULT_FALLBACK_H +#define FLOAXIE_DEFAULT_FALLBACK_H + +#include +#include + +#include + +namespace floaxie +{ + /** \brief Function template to wrap C Standard Library floating point + * parse function based on floating-point type and character type (normal + * or wide). + * + * \tparam FloatType floating point type to parse. + * \tparam CharType character type of string to parse. + */ + template FloatType default_fallback(const CharType* str, CharType** str_end); + + /** \brief `float` and `char`. */ + template<> inline float default_fallback(const char* str, char** str_end) + { + return std::strtof(str, str_end); + } + + /** \brief `double` and `char`. */ + template<> inline double default_fallback(const char* str, char** str_end) + { + return std::strtod(str, str_end); + } + + /** \brief `long double` and `char`. */ + template<> inline long double default_fallback(const char* str, char** str_end) + { + return std::strtold(str, str_end); + } + + /** \brief `float` and `wchar_t`. */ + template<> inline float default_fallback(const wchar_t* str, wchar_t** str_end) + { + return std::wcstof(str, str_end); + } + + /** \brief `double` and `wchar_t`. */ + template<> inline double default_fallback(const wchar_t* str, wchar_t** str_end) + { + return std::wcstod(str, str_end); + } + + /** \brief `long double` and `wchar_t`. */ + template<> inline long double default_fallback(const wchar_t* str, wchar_t** str_end) + { + return std::wcstold(str, str_end); + } + + /** \brief Returns `conversion_status` based on `errno` value. + * + * Analyzes current value of `errno` together with the passed conversion + * result and returns `conversion_status` value for the case. + * + * \tparam FloatType floating-point type of the returned value passed. + * + * \p returned_value the value returned after the conversion. + * + * \return status of the last conversion. + * + * \sa `conversion_status` + */ + template conversion_status check_errno(FloatType returned_value) + { + if (errno != ERANGE) + return conversion_status::success; + + return returned_value ? conversion_status::overflow : conversion_status::underflow; + } +} + +#endif // FLOAXIE_DEFAULT_FALLBACK_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/diy_fp.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/diy_fp.h new file mode 100644 index 000000000..82226db15 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/diy_fp.h @@ -0,0 +1,518 @@ +/* + * Copyright 2015-2019 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * diy_fp class and helper functions use code and influenced by + * Florian Loitsch's original Grisu algorithms implementation + * (http://florian.loitsch.com/publications/bench.tar.gz) + * and "Printing Floating-Point Numbers Quickly and Accurately with + * Integers" paper + * (http://florian.loitsch.com/publications/dtoa-pldi2010.pdf) + */ + +#ifndef FLOAXIE_DIY_FP_H +#define FLOAXIE_DIY_FP_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace floaxie +{ + + /** \brief Template structure to define `diy_fp` inner types for + * selected floating point types. + */ + template struct diy_fp_traits; + + /** \brief `diy_fp_traits` specialization associated with single precision + * floating point type (`float`). + * + * **Mantissa** is stored using the fastest natively supported standard + * integer type — almost always it's 32-bit unsigned integer value. + * + * **Exponent** is stored in `int` value. Shorter types aren't used to + * avoid any performance impacts of not using default integer types + * (though it can be effective in terms of cache usage). + */ + template<> struct diy_fp_traits + { + /** \brief Integer type to store mantissa value. */ + typedef std::uint32_t mantissa_type; + /** \brief Integer type to store exponent value. */ + typedef int exponent_type; + }; + + /** \brief `diy_fp_traits` specialization associated with double precision + * floating point type (`double`). + * + * **Mantissa** is stored using the biggest natively supported standard + * integer type — currently it's 64-bit unsigned integer value. Emulated + * (e.g. *big integer*) integer types are not used, as they don't follow + * the ideas behind the fast integer algorithms (they are slower due to + * the emulation part). + * + * **Exponent** is stored in `int` value. + * + */ + template<> struct diy_fp_traits + { + /** \brief Integer type to store mantissa value. */ + typedef std::uint64_t mantissa_type; + /** \brief Integer type to store exponent value. */ + typedef int exponent_type; + }; + + /** \brief Integer representation of floating point value. + * + * The templated type represents floating point value using two integer values, one + * to store **mantissa** and another to hold **exponent**. Concrete types are + * expressed as **diy_fp** specializations, with pre-defined types for **mantissa** + * and **exponent**, suitable to process floating point value of the specified + * precision with maximum efficiency and without losing accuracy. + * + * \tparam FloatType floating point type the representation is instantiated for. + * \tparam Traits some inner settings (mainly, integer types to store mantissa and + * exponent) corresponding to `FloatType`. + * + * The type is used in **Grisu** and **Krosh** algorithms. + */ + template> class diy_fp + { + public: + /** \brief Mantissa storage type abstraction. */ + typedef typename Traits::mantissa_type mantissa_storage_type; + /** \brief Exponent storage type abstraction. */ + typedef typename Traits::exponent_type exponent_storage_type; + + private: + static_assert(std::numeric_limits::is_iec559, "Only IEEE-754 floating point types are supported"); + static_assert(sizeof(FloatType) == sizeof(mantissa_storage_type), "Float type is not compatible with its `diy_fp` representation layout."); + + /** \brief Returns value of hidden bit for the specified floating point type. + * + * \return integer value of hidden bit of the specified type in + * `mantissa_storage_type` variable. + */ + static constexpr mantissa_storage_type hidden_bit() + { + return raised_bit(std::numeric_limits::digits - 1); + } + + public: + /** \brief Default constructor. */ + diy_fp() = default; + + /** \brief Copy constructor. */ + diy_fp(const diy_fp&) = default; + + /** \brief Component initialization constructor. */ + constexpr diy_fp(mantissa_storage_type mantissa, exponent_storage_type exponent) noexcept : m_f(mantissa), m_e(exponent) { } + + /** \brief Initializes `diy_fp` value from the value of floating point + * type. + * + * It splits floating point value into mantissa and exponent + * components, calculates hidden bit of mantissa and initializes + * `diy_fp` value with the results of calculations. + */ + explicit diy_fp(FloatType d) noexcept + { + constexpr auto full_mantissa_bit_size(std::numeric_limits::digits); + constexpr auto mantissa_bit_size(full_mantissa_bit_size - 1); // remember hidden bit + constexpr mantissa_storage_type mantissa_mask(mask(mantissa_bit_size)); + constexpr mantissa_storage_type exponent_mask((~mantissa_mask) ^ msb_value()); // ignore sign bit + constexpr exponent_storage_type exponent_bias(std::numeric_limits::max_exponent - 1 + mantissa_bit_size); + + mantissa_storage_type parts = type_punning_cast(d); + + m_f = parts & mantissa_mask; + m_e = (parts & exponent_mask) >> mantissa_bit_size; + + if (m_e) + { + m_f += hidden_bit(); + m_e -= exponent_bias; + } + else + { + m_e = 1 - exponent_bias; + } + } + + /** \brief Downsample result structure. + * + */ + struct downsample_result + { + /** \brief Downsampled floating point result. */ + FloatType value; + /** \brief Status showing possible under- or overflow found during downsampling. */ + conversion_status status; + /** \brief Flag indicating if the conversion is accurate (no + * [rounding errors] (http://www.exploringbinary.com/decimal-to-floating-point-needs-arbitrary-precision/). */ + bool is_accurate; + }; + + /** \brief Convert `diy_fp` value back to floating point type correctly + * downsampling mantissa value. + * + * The caller should ensure, that the current mantissa value is not null + * and the whole `diy_fp` value is normalized, otherwise the behaviour is + * undefined. + * + * \return result structure with floating point value of the specified type. + */ + downsample_result downsample() + { + downsample_result ret; + + ret.is_accurate = true; + ret.status = conversion_status::success; + + assert(m_f != 0); + + assert(is_normalized()); + + constexpr auto full_mantissa_bit_size(std::numeric_limits::digits); + constexpr auto mantissa_bit_size(full_mantissa_bit_size - 1); // remember hidden bit + constexpr mantissa_storage_type my_mantissa_size(bit_size()); + constexpr mantissa_storage_type mantissa_mask(mask(mantissa_bit_size)); + constexpr exponent_storage_type exponent_bias(std::numeric_limits::max_exponent - 1 + mantissa_bit_size); + constexpr std::size_t lsb_pow(my_mantissa_size - full_mantissa_bit_size); + + const auto f(m_f); + + if (m_e >= std::numeric_limits::max_exponent) + { + ret.value = huge_value(); + ret.status = conversion_status::overflow; + return ret; + } + + if (m_e + int(my_mantissa_size) < std::numeric_limits::min_exponent - int(mantissa_bit_size)) + { + ret.value = FloatType(0); + ret.status = conversion_status::underflow; + return ret; + } + + const std::size_t denorm_exp(positive_part(std::numeric_limits::min_exponent - int(mantissa_bit_size) - m_e - 1)); + + assert(denorm_exp < my_mantissa_size); + + const std::size_t shift_amount(std::max(denorm_exp, lsb_pow)); + + mantissa_storage_type parts = m_e + shift_amount + exponent_bias - (denorm_exp > lsb_pow); + parts <<= mantissa_bit_size; + + const auto& round(round_up(f, shift_amount)); + parts |= ((f >> shift_amount) + round.value) & mantissa_mask; + + ret.value = type_punning_cast(parts); + ret.is_accurate = round.is_accurate; + + return ret; + } + + /** \brief Mantissa component. */ + constexpr mantissa_storage_type mantissa() const + { + return m_f; + } + + /** \brief Exponent component. */ + constexpr exponent_storage_type exponent() const + { + return m_e; + } + + /** \brief Checks if the value is normalized. + * + * The behaviour is undefined, if called for null value. + * + */ + bool is_normalized() const noexcept + { + assert(m_f != 0); // normalization of zero is undefined + return m_f & msb_value(); + } + + + /** \brief Normalizes the value the common way. + * + * The caller should ensure, that the current mantissa value is not null, + * otherwise the behaviour is undefined. + */ + void normalize() noexcept + { + assert(m_f != 0); // normalization of zero is undefined + + while (!highest_bit(m_f)) + { + m_f <<= 1; + m_e--; + } + } + + /** \brief Copy assignment operator. */ + diy_fp& operator=(const diy_fp&) = default; + + /** \brief Subtracts the specified `diy_fp` value from the current. + * + * Simple mantissa subtraction of `diy_fp` values. + * + * If exponents of the values differ or mantissa of left value is less, + * than mantissa of right value, the behaviour is undefined. + * + * \param rhs subtrahend. + * + * \return reference to current value, i.e. the result of the + * subtraction. + */ + diy_fp& operator-=(const diy_fp& rhs) noexcept + { + assert(m_e == rhs.m_e && m_f >= rhs.m_f); + + m_f -= rhs.m_f; + + return *this; + } + + /** \brief Non-destructive version of `diy_fp::operator-=()`. */ + diy_fp operator-(const diy_fp& rhs) const noexcept + { + return diy_fp(*this) -= rhs; + } + + /** \brief Fast and coarse multiplication. + * + * Performs multiplication of `diy_fp` values ignoring some carriers + * for the sake of performance. This multiplication algorithm is used + * in original **Grisu** implementation and also works fine for + * **Krosh**. + * + * \param rhs multiplier. + * + * \return reference to current value, i.e. the result of the + * multiplication. + */ + diy_fp& operator*=(const diy_fp& rhs) noexcept + { + constexpr std::size_t half_width = bit_size() / 2; + constexpr auto mask_half = mask(half_width); + + const mantissa_storage_type a = m_f >> half_width; + const mantissa_storage_type b = m_f & mask_half; + const mantissa_storage_type c = rhs.m_f >> half_width; + const mantissa_storage_type d = rhs.m_f & mask_half; + + const mantissa_storage_type ac = a * c; + const mantissa_storage_type bc = b * c; + const mantissa_storage_type ad = a * d; + const mantissa_storage_type bd = b * d; + + const mantissa_storage_type tmp = (bd >> half_width) + (ad & mask_half) + (bc & mask_half) + raised_bit(half_width - 1); + + m_f = ac + (ad >> half_width) + (bc >> half_width) + (tmp >> half_width); + m_e += rhs.m_e + bit_size(); + + return *this; + } + + /** \brief Non-destructive version of `diy_fp::operator*=()`. */ + diy_fp operator*(const diy_fp& rhs) const noexcept + { + return diy_fp(*this) *= rhs; + } + + /** \brief Increment (prefix) with mantissa overflow control. */ + diy_fp& operator++() noexcept + { + if (m_f < std::numeric_limits::max()) + { + ++m_f; + } + else + { + m_f >>= 1; + ++m_f; + ++m_e; + } + return *this; + } + + /** \brief Postfix increment version. */ + diy_fp operator++(int) noexcept + { + auto temp = *this; + ++(*this); + return temp; + } + + /** \brief Decrement (prefix) with mantissa underflow control. */ + diy_fp& operator--() noexcept + { + if (m_f > 1) + { + --m_f; + } + else + { + m_f <<= 1; + --m_f; + --m_e; + } + return *this; + } + + /** \brief Postfix decrement version. */ + diy_fp operator--(int) noexcept + { + auto temp = *this; + --(*this); + return temp; + } + + /** \brief Equality of `diy_fp` values. + * + * Just member-wise equality check. + */ + bool operator==(const diy_fp& d) const noexcept + { + return m_f == d.m_f && m_e == d.m_e; + } + + + /** \brief Inequality of `diy_fp` values. + * + * Negation of `diy_fp::operator==()` for consistency. + */ + bool operator!=(const diy_fp& d) const noexcept + { + return !operator==(d); + } + + /** \brief Calculates boundary values (M+ and M-) for the specified + * floating point value. + * + * Helper function for **Grisu2** algorithm, which first converts the + * specified floating point value to `diy_fp` and then calculates lower + * (M-) and higher (M+) boundaries of it and thus of original accurate + * floating point value. + * + * These two boundary values define the range where all the values are + * correctly rounded to the specified floating point value, so any + * value within this range can be treated as correct representation of + * the specified one. + * + * \param d floating point value to calculate boundaries for. + * + * \return `std::pair` of two `diy_fp` values, **M-** and **M+**, + * respectively. + * + * \see [Printing Floating-Point Numbers Quickly and Accurately with + * Integers] + * (http://florian.loitsch.com/publications/dtoa-pldi2010.pdf) + */ + static std::pair boundaries(FloatType d) noexcept + { + std::pair result; + diy_fp &mi(result.first), &pl(result.second); + pl = diy_fp(d); + mi = pl; + + pl.m_f <<= 1; + pl.m_f += 1; + + pl.m_e -= 1; + + + pl.normalize_from_ieee754(); // as we increase precision of IEEE-754 type by 1 + + const unsigned char shift_amount(1 + (mi.m_f == hidden_bit())); + + mi.m_f <<= shift_amount; + mi.m_f -= 1; + mi.m_e -= shift_amount; + + mi.m_f <<= mi.m_e - pl.m_e; + mi.m_e = pl.m_e; + + return result; + } + + /** \brief Prints `diy_fp` value. + * + * \param os `std::basic_ostream` to print to. + * \param v `diy_fp` value to print. + * + * \return `std::basic_ostream` with the \p **v** value printed. + */ + template + friend std::basic_ostream& operator<<(std::basic_ostream& os, const diy_fp& v) + { + os << "(f = " << print_binary(v.m_f) << ", e = " << v.m_e << ')'; + return os; + } + + private: + /** \brief Normalizes the value using additional information on + * mantissa content of the `FloatType`. + + * Mantissa value is treated as of the width defined in + * `std::numeric_limits`. This information speeds up the normalization, + * allowing to shift the value by several positions right at one take, + * rather than shifting it by one step and checking if it's still not normalized. + * + * The caller should ensure, that the current mantissa value is not null + * and is really represented in IEEE-754 format, otherwise the behaviour + * is undefined. + */ + void normalize_from_ieee754() noexcept + { + constexpr auto mantissa_bit_width(std::numeric_limits::digits); + + static_assert(mantissa_bit_width >= 0, "Mantissa bit width should be positive."); + + assert(m_f != 0); // normalization of zero is undefined + + while (!nth_bit(m_f, mantissa_bit_width)) + { + m_f <<= 1; + m_e--; + } + + constexpr mantissa_storage_type my_mantissa_size(bit_size()); + constexpr mantissa_storage_type e_diff = my_mantissa_size - mantissa_bit_width - 1; + + m_f <<= e_diff; + m_e -= e_diff; + } + + mantissa_storage_type m_f; + exponent_storage_type m_e; + }; +} + +#endif // FLOAXIE_DIY_FP_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/fraction.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/fraction.h new file mode 100644 index 000000000..64cf206c5 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/fraction.h @@ -0,0 +1,142 @@ +/* + * Copyright 2015, 2016 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOAXIE_FRACTION_H +#define FLOAXIE_FRACTION_H + +#include + +#include + +namespace floaxie +{ + /** \brief Recursive structure template used to convert decimal common + * fraction to binary common fraction. + * + * The structure template hierarchy is used to calculate the binary common + * fraction value, which is approximately equal to given decimal common + * fraction. + * + * Example: + * ~~~ + * 0.625 = 0.101 + * ~~~ + * + * \tparam T numeric type of input value and result. + * \tparam decimal_digits number of significant decimal digits in + * numerator (equals to the power of ten in denominator); for example, for + * 0.625 \p **decimal_digits** is 3. + * \tparam binary_digits number of significant binary digits in numerator + * of the result, which is defined by the necessary accuracy of + * result approximation; for example, for 0.101 \p **decimal_digits** is 3. + * \tparam current_binary_digit template cursor used to implement + * recursive descent. + * \tparam terminal automatically calculated flag of recursion termination. + */ + template + < + typename T, + std::size_t decimal_digits, + std::size_t binary_digits, + std::size_t current_binary_digit, + bool terminal = (binary_digits == current_binary_digit) + > + struct fraction_converter; + + /** \brief Intermediate step implementation of `fraction_converter` + * template. + */ + template + < + typename T, + std::size_t decimal_digits, + std::size_t binary_digits, + std::size_t current_binary_digit + > + struct fraction_converter + { + /** \brief Calculates \p `current_binary_digit`-th digit of the result. + * + * \param decimal_numerator value of decimal numerator of common + * fraction to convert. + * + * \return properly shifted value of \p `current_binary_digit`-th + * digit of the result. + */ + static T convert(T decimal_numerator) + { + constexpr T numerator(static_pow<10, decimal_digits>()); + constexpr T denominator(static_pow<2, current_binary_digit>()); + constexpr T decimal_fraction(numerator / denominator); + + constexpr std::size_t shift_amount(binary_digits - current_binary_digit); + + const T decision(decimal_numerator >= decimal_fraction); + const T residue(decimal_numerator - decision * decimal_fraction); + + return (decision << shift_amount) | + fraction_converter::convert(residue); + } + }; + + /** \brief Terminal step implementation of `fraction_converter` template. + * + */ + template + < + typename T, + std::size_t decimal_digits, + std::size_t binary_digits, + std::size_t current_binary_digit + > + struct fraction_converter + { + /** \brief Calculates least significant digit of the result. + * + * \param decimal_numerator value of decimal numerator of common + * fraction to convert. + * + * \return right most (least significant) digit of the result. + */ + static T convert(T decimal_numerator) + { + constexpr T numerator(static_pow<10, decimal_digits>()); + constexpr T denominator(static_pow<2, current_binary_digit>()); + constexpr T decimal_fraction(numerator / denominator); + + return decimal_numerator >= decimal_fraction; + } + }; + + /** \brief Wrapper function to convert numerator of decimal common fraction + * to numerator of approximately equal binary common fraction with the + * specified accuracy. + * + * \tparam decimal_digits number of digits in decimal numerator to observe + * (input accuracy). + * \tparam binary_digits number of digits in binary numerator to generate + * (output accuracy). + * + * \return value of binary numerator with the specified accuracy as + * calculated by `fraction_converter`. + */ + template inline T convert_numerator(T decimal_numerator) + { + return fraction_converter::convert(decimal_numerator); + } +} + +#endif // FLOAXIE_FRACTION_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/ftoa.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/ftoa.h new file mode 100644 index 000000000..cc6dbe297 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/ftoa.h @@ -0,0 +1,202 @@ +/* + * Copyright 2015-2022 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOAXIE_FTOA_H +#define FLOAXIE_FTOA_H + +#include +#include +#include +#include +#include + +#include +#include + +namespace floaxie +{ + /** \brief Returns maximum size of buffer can ever be required by `ftoa()`. + * + * Maximum size of buffer passed to `ftoa()` guaranteed not to lead to + * undefined behaviour. + * + * \tparam FloatType floating point type, which value is planned to be + * printed to the buffer. + * + * \return maximum size of buffer, which can ever be used in the very worst + * case. + */ + template constexpr std::size_t max_buffer_size() noexcept + { + typedef typename std::decay::type FloatType; + + // digits, '.' (or 'e' plus three-digit power with optional sign) and '\0' + return max_digits() + 1 + 1 + 3 + 1; + } + + /** \brief Prints floating point value to optimal string representation. + * + * The function prints the string representation of the specified floating + * point value using + * [**Grisu2**](http://florian.loitsch.com/publications/dtoa-pldi2010.pdf) + * algorithm and tries to get it as shorter, as possible. Usually it + * succeeds, but sometimes fails, and the output representation is not + * the shortest for this value. For the sake of speed improvement this is + * ignored, while there's **Grisu3** algorithm which rules this out + * informing the caller of the failure, so that it can call slower, but + * more accurate algorithm in this case. + * + * The format of the string representation is one of the following: + * 1. Decimal notation, which contains: + * - minus sign ('-') in case of negative value + * - sequence of one or more decimal digits optionally containing + * decimal point character ('.') + * 2. Decimal exponent notation, which contains: + * - minus ('-') sign in case of negative value + * - sequence of one or more decimal digits optionally containing + * decimal point character ('.') + * - 'e' character followed by minus sign ('-') in case of negative + * power of the value (i.e. the specified value is < 1) and + * sequence of one, two of three decimal digits. + * + * \tparam FloatType type of floating point value, calculated using passed + * input parameter \p **v**. + * \tparam CharType character type (typically `char` or `wchar_t`) of the + * output buffer \p **buffer**. + * + * \param v floating point value to print. + * \param buffer character buffer of enough size (see `max_buffer_size()`) + * to print the representation to. + * + * \return number of characters actually written. + * + * \see `max_buffer_size()` + */ + template inline std::size_t ftoa(FloatType v, CharType* buffer) noexcept + { + if (std::isnan(v)) + { + buffer[0] = 'n'; + buffer[1] = 'a'; + buffer[2] = 'n'; + buffer[3] = '\0'; + + return 3; + } + else if (std::isinf(v)) + { + if (v > 0) + { + buffer[0] = 'i'; + buffer[1] = 'n'; + buffer[2] = 'f'; + buffer[3] = '\0'; + + return 3; + } + else + { + buffer[0] = '-'; + buffer[1] = 'i'; + buffer[2] = 'n'; + buffer[3] = 'f'; + buffer[4] = '\0'; + + return 4; + } + } + else if (v == 0) + { + buffer[0] = '0'; + buffer[1] = '\0'; + + return 1; + } + else + { + *buffer = '-'; + buffer += v < 0; + + constexpr int alpha(grisu_parameters.alpha), gamma(grisu_parameters.gamma); + constexpr unsigned int decimal_scientific_threshold(16); + + int len, K; + + grisu2(v, buffer, &len, &K); + return (v < 0) + prettify(buffer, len, K); + } + } + + /** \brief Prints floating point value to optimal representation in + * `std::basic_string`. + * + * Wrapper function around `ftoa()`, which returns `std::basic_string`, + * rather than writing to the specified character buffer. This may be + * more useful, if working with `std::basic_string` strings is preferred. + * Please note, however, than common usage scenarios might be significantly + * slower, when many `std::basic_string`'s are created with this function + * and concatenated to each other, than when the outputs of `ftoa()` calls + * are written to one long buffer. + * + * \tparam FloatType type of floating point value, calculated using passed + * input parameter \p **v**. + * \tparam CharType character type (typically `char` or `wchar_t`) of the + * output buffer \p **buffer**. + * + * \param v floating point value to print. + * \param buffer character buffer of enough size (see `max_buffer_size()`) + * to print the representation to. + * + * \see `ftoa()` + */ + template inline std::basic_string to_basic_string(FloatType v) + { + std::basic_string result(max_buffer_size(), CharType()); + + ftoa(v, &result.front()); + + result.resize(std::char_traits::length(result.data())); + result.shrink_to_fit(); + + return result; + } + + /** \brief 'Specialization' of `to_basic_string()` template for `std::string`. */ + template inline std::string to_string(FloatType v) + { + return to_basic_string(v); + } + + /** \brief 'Specialization' of `to_basic_string()` template for `std::wstring`. */ + template inline std::wstring to_wstring(FloatType v) + { + return to_basic_string(v); + } + + /** \brief 'Specialization' of `to_basic_string()` template for `std::u16string`. */ + template inline std::u16string to_u16string(FloatType v) + { + return to_basic_string(v); + } + + /** \brief 'Specialization' of `to_basic_string()` template for `std::u32string`. */ + template inline std::u32string to_u32string(FloatType v) + { + return to_basic_string(v); + } +} + +#endif // FLOAXIE_FTOA_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/grisu.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/grisu.h new file mode 100644 index 000000000..d2e2449b8 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/grisu.h @@ -0,0 +1,303 @@ +/* + * Copyright 2015-2019 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * digin_gen(), grisu() functions and kappa precalculation approach + * were greatly influenced by + * Florian Loitsch's original Grisu algorithms implementation + * (http://florian.loitsch.com/publications/bench.tar.gz), + * "Printing Floating-Point Numbers Quickly and Accurately with + * Integers" paper + * (http://florian.loitsch.com/publications/dtoa-pldi2010.pdf) + * and Milo Yip's Grisu implementation + * (https://github.com/miloyip/dtoa-benchmark) + */ + +#ifndef FLOAXIE_GRISU_H +#define FLOAXIE_GRISU_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace floaxie +{ + /** \brief Half of `diy_fp::mantissa_storage_type` to use in calculations. + */ + typedef std::uint32_t half_of_mantissa_storage_type; + + /** \brief `std::pair` containing exponent value and the corresponding + * power of 10. + */ + typedef std::pair kappa_pair_type; + + /** \brief Bind exponent value and the corresponding power of 10. */ + template constexpr kappa_pair_type make_kappa_div() + { + return kappa_pair_type(pow, static_pow<10, pow - 1>()); + } + + /** \brief Ground specialization of `make_kappa_div()` template. */ + template<> constexpr kappa_pair_type make_kappa_div<0>() + { + return kappa_pair_type(0, 1); + } + + /** \brief Calculate the biggest power of 10 divisor for the specified + * value. + */ + constexpr kappa_pair_type calculate_kappa_div(half_of_mantissa_storage_type n) noexcept + { + if (n < static_pow<10, 1>()) return make_kappa_div<1>(); + if (n < static_pow<10, 2>()) return make_kappa_div<2>(); + if (n < static_pow<10, 3>()) return make_kappa_div<3>(); + if (n < static_pow<10, 4>()) return make_kappa_div<4>(); + if (n < static_pow<10, 5>()) return make_kappa_div<5>(); + if (n < static_pow<10, 6>()) return make_kappa_div<6>(); + if (n < static_pow<10, 7>()) return make_kappa_div<7>(); + if (n < static_pow<10, 8>()) return make_kappa_div<8>(); + if (n < static_pow<10, 9>()) return make_kappa_div<9>(); + return make_kappa_div<10>(); + } + + /** \brief Defines the maximum possible number of digits to be generated by + * the algorithm. + * + * \tparam FloatType floating point type to calculate the maximum number for. + */ + template constexpr std::size_t max_digits() noexcept + { + return std::numeric_limits::digits10 + + std::numeric_limits::mantissa_storage_type>::digits10; + } + + template struct digit_gen_select; + + + /** \brief Template class to select `digit_gen` implementation avoiding + * function template partial specialization. + */ + template<> struct digit_gen_select + { + /** \brief Function of digit generation for the case of α < 0 and γ < 0. + * + * This is probably the fastest `digit_gen()` algorithm, which implies, + * that both **M+** and **M-** exponents are not positive. + * + * Implemented as static member to solve the problem of partial specialization + * of `digit_gen()`. + * + * \tparam FloatType floating point type of `diy_fp` (`float` for single precision, + * `double` for double precision) of \p **Mm** and \p **Mp**. + * \tparam CharType character type (typically `char` or `wchar_t`) of \p **buffer**. + * + * \param buffer - buffer where digits are generated to. + * \param len - output parameter to pass the length of the digit sequence written. + * \param K - input/output parameter to reflect K modifications made. + * + * \see [Printing Floating-Point Numbers Quickly and Accurately with + * Integers] + * (http://florian.loitsch.com/publications/dtoa-pldi2010.pdf) + */ + template + inline static void gen(const diy_fp& Mp, const diy_fp& Mm, CharType* buffer, int* len, int* K) noexcept + { + assert(Mp.exponent() <= 0); + + const diy_fp& delta(Mp - Mm); + + const diy_fp one(raised_bit::mantissa_storage_type>(-Mp.exponent()), Mp.exponent()); + + half_of_mantissa_storage_type p1 = Mp.mantissa() >> -one.exponent(); + typename diy_fp::mantissa_storage_type p2 = Mp.mantissa() & (one.mantissa() - 1); + + assert(p1 || p2); + + *len = 0; + + auto delta_f = delta.mantissa(); + + const bool close_to_delta = p2 <= delta_f; + + if (p1) + { + auto&& kappa_div(calculate_kappa_div(p1)); + + unsigned char& kappa(kappa_div.first); + half_of_mantissa_storage_type& div(kappa_div.second); + + while (kappa > 0 && p1) + { + buffer[(*len)++] = '0' + p1 / div; + + p1 %= div; + + kappa--; + div /= 10; + } + + if (close_to_delta) + { + *K += kappa; + return; + } + else + { + wrap::memset(buffer + (*len), CharType('0'), kappa); + (*len) += kappa; + } + } + + const bool some_already_written = (*len) > 0; + unsigned char kappa(0); + + while (p2 > delta_f) + { + p2 *= 10; + + const unsigned char d = p2 >> -one.exponent(); + + if (some_already_written || d) + buffer[(*len)++] = '0' + d; + + p2 &= one.mantissa() - 1; + + ++kappa; + delta_f *= 10; + } + + *K -= kappa; + } + }; + + + /** \brief Digit generation function template. + * + * Digit generation algorithm tries to find the value in the range of + * **(M-, M+)** with the shortest possible string representation, which is + * then treated as the representation of the original value, since it + * converts back to it. + * + * General (slow) implementation is of little use, but the optimized + * versions of the function relies so much on α and γ values used in + * **Grisu** algorithm. This implementation requires both α and γ be + * greater of equal, than the bit size of `diy_fp::mantissa_storage_type` + * to use the most optimal `digit_gen()` implementation. + * + * \tparam alpha α value of **Grisu** algorithm. + * \tparam gamma γ value of **Grisu** algorithm. + * \tparam CharType character type (typically `char` or `wchar_t`) of the + * output buffer \p **buffer**. + * + * \param Mp **M+** value (right boundary). + * \param Mm **M-** value (left boundary). + * \param buffer character buffer to print the representation to + * \param len output parameter to return the length of the representation + * printed. + * \param K output parameter to return **K** (decimal exponent) of the + * value. + * + * \see `max_digits()` + * \see `max_buffer_size()` + * \see [Printing Floating-Point Numbers Quickly and Accurately with + * Integers] + * (http://florian.loitsch.com/publications/dtoa-pldi2010.pdf) + */ + template + inline void digit_gen(const diy_fp& Mp, const diy_fp& Mm, CharType* buffer, int* len, int* K) noexcept + { + static_assert(static_cast(constexpr_abs(alpha)) >= bit_size::mantissa_storage_type>() / 2 && + static_cast(constexpr_abs(gamma)) >= bit_size::mantissa_storage_type>() / 2, + "Current implementation supports only α and γ, which absolute values are equal or higher, " + "than a half of integer mantissa bit size (typically 32) for performance reasons."); + + constexpr bool exponent_is_positive = alpha > 0 && gamma > 0; + digit_gen_select::gen(Mp, Mm, buffer, len, K); + } + + /** \brief **Grisu2** algorithm implementation. + * + * \tparam alpha α value of **Grisu** algorithm. + * \tparam gamma γ value of **Grisu** algorithm. + * \tparam FloatType type of input floating-point value (calculated by type + * of \p **v** parameter). + * \tparam CharType character type (typically `char` or `wchar_t`) of the + * output buffer \p **buffer**. + * + * \param v floating point value to print. + * \param buffer large enough character buffer to print to. + * \param length output parameter to return the length of printed + * representation. + * \param K output parameter to return **K** (decimal exponent) of the + * value. + * + * \see `max_digits()` + * \see `max_buffer_size()` + * \see [Printing Floating-Point Numbers Quickly and Accurately with + * Integers] + * (http://florian.loitsch.com/publications/dtoa-pldi2010.pdf) + */ + template inline void grisu2(FloatType v, CharType* buffer, int* length, int* K) noexcept + { + static_assert(alpha <= gamma - 3, + "It's imposed that γ ⩾ α + 3, since otherwise it's not always possible to find a proper decimal cached power"); + + std::pair, diy_fp>&& w(diy_fp::boundaries(v)); + diy_fp &w_m(w.first), &w_p(w.second); + + const int mk = k_comp_exp(w_p.exponent()); + const diy_fp& c_mk(cached_power(mk)); + + w_m *= c_mk; + w_p *= c_mk; + + ++w_m; + --w_p; + + *K = -mk; + + digit_gen(w_p, w_m, buffer, length, K); + } + + /** \brief Structure to hold Grisu algorithm parameters, **α** and **γ**. */ + struct parameters + { + int alpha, gamma; + }; + + /** \brief Template variable of Grisu algorithm parameters. + * + * Used to provide proper values of Grisu algorithm for the specified + * floating point type. + */ + template constexpr parameters grisu_parameters + { + -(int(bit_size() / 2 + 3)), // α + -int(bit_size() / 2) // γ + }; +} + +#endif // FLOAXIE_GRISU_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/huge_val.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/huge_val.h new file mode 100644 index 000000000..5822a5862 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/huge_val.h @@ -0,0 +1,57 @@ +/* + * Copyright 2015, 2016, 2017 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOAXIE_HUGE_VAL_H +#define FLOAXIE_HUGE_VAL_H + +#include +#include + +namespace floaxie +{ + /** \brief Variable template to unify getting `HUGE_VAL` for + * different floating point types in parameterized manner. + * + * \tparam FloatType floating point type to get `HUGE_VAL` for. + * + * \see [HUGE_VALF, HUGE_VAL, HUGE_VALL] + * (http://en.cppreference.com/w/cpp/numeric/math/HUGE_VAL) + */ + template constexpr inline FloatType huge_value() noexcept + { + return std::numeric_limits::infinity(); + } + + /** \brief `float`. */ + template<> constexpr inline float huge_value() noexcept + { + return HUGE_VALF; + } + + /** \brief `double`. */ + template<> constexpr inline double huge_value() noexcept + { + return HUGE_VAL; + } + + /** \brief `long double`. */ + template<> constexpr inline long double huge_value() noexcept + { + return HUGE_VALL; + } +} + +#endif // FLOAXIE_HUGE_VAL_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/integer_of_size.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/integer_of_size.h new file mode 100644 index 000000000..7430c3976 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/integer_of_size.h @@ -0,0 +1,51 @@ +/* + * Copyright 2015, 2016 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOAXIE_INTEGER_OF_SIZE_H +#define FLOAXIE_INTEGER_OF_SIZE_H + +#include +#include + +namespace floaxie +{ + /** \brief Identity type — hold the specified type in internal `typedef`. + * + * \tparam T type to hold. + */ + template struct identity + { + /** \brief Held type. */ + typedef T type; + }; + + /** \brief Maps some of unsigned integer types to their sizes. + * + * Useful for choosing unsigned integer type of the same width as some + * target type (e.g. floating point) to increase possible accuracy. + * + * \tparam size size in bytes of the desired type. + */ + template struct integer_of_size : identity {}; + + /** \brief Specialization for 64-bit unsigned integer. */ + template<> struct integer_of_size : identity {}; + + /** \brief Specialization for 32-bit unsigned integer. */ + template<> struct integer_of_size : identity {}; +} + +#endif // FLOAXIE_INTEGER_OF_SIZE_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/k_comp.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/k_comp.h new file mode 100644 index 000000000..ee4ba4bd8 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/k_comp.h @@ -0,0 +1,54 @@ +/* + * Copyright 2015-2019 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * k_comp() function uses code taken from + * Florian Loitsch's "Printing Floating-Point Numbers Quickly + * and Accurately with Integers" paper + * (http://florian.loitsch.com/publications/dtoa-pldi2010.pdf) + */ + +#ifndef FLOAXIE_K_COMP_H +#define FLOAXIE_K_COMP_H + +namespace floaxie +{ + /** \brief Compiled value of \f$log{10} 2 \f$ */ + constexpr auto lg_2(0.30102999566398114); // 1 / log2(10) = lg(2) ≈ 0.30102999566398114 + + /** \brief Calculate **K** decimal exponent value by binary exponent. + * + * We ignore mantissa component (q) in exponent to eliminate + * excessive add and subtract of it during K computation. + * + * Function name was changed to not confuse it with the original + * k_comp() function from reference paper where this component + * is considered. + * + * \tparam alpha α value of **Grisu** algorithm. + * \tparam gamma γ value of **Grisu** algorithm. + * + * \param e binary exponent of the floating point value. + * + * \see [Printing Floating-Point Numbers Quickly and Accurately with + * Integers] + * (http://florian.loitsch.com/publications/dtoa-pldi2010.pdf) + */ + template constexpr int k_comp_exp(int e) + { + return (alpha - e - 1) * lg_2 + (e + 1 < alpha); + } +} + +#endif // FLOAXIE_K_COMP_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/krosh.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/krosh.h new file mode 100644 index 000000000..c002459e4 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/krosh.h @@ -0,0 +1,621 @@ +/* + * Copyright 2015-2019 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOAXIE_CROSH_H +#define FLOAXIE_CROSH_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace floaxie +{ + /** \brief Maximum number of decimal digits mantissa of `diy_fp` can hold. */ + template constexpr std::size_t decimal_q = std::numeric_limits::mantissa_storage_type>::digits10; + + /** \brief Maximum number of necessary binary digits of fraction part. */ + constexpr std::size_t fraction_binary_digits(7); + + /** \brief Maximum number of decimal digits of fraction part, which can be observed. */ + constexpr std::size_t fraction_decimal_digits(4); + + /** \brief Maximum length of input string (2 KB). */ + constexpr std::size_t maximum_offset = 2048; + + /** \brief Maximum number of decimal digits in the exponent value. */ + constexpr std::size_t exponent_decimal_digits(3); + + /** \brief Tries to find and eat NaN representation in one of two forms. + * + * Searches for either "NAN" or "NAN()" form of NaN + * (not a number) value representation (case insensitive) to help + * converting it to quiet NaN and finding the end of the read value. + * + * \param str character buffer to analyze. + * + * \return number of consumed characters. Naturally, if it's equal to zero, + * NaN representation wasn't found. + */ + template std::size_t eat_nan(const CharType* str) noexcept + { + std::size_t eaten(0); + + if ((str[0] == 'a' || str[0] == 'A') && (str[1] == 'n' || str[1] == 'N')) + { + const CharType* cp = str + 2; + eaten = 2; + + /* Match `(n-char-sequence-digit)'. */ + if (*cp == '(') + { + do + ++cp; + while ((*cp >= '0' && *cp <= '9') || + (std::tolower(*cp, std::locale()) >= 'a' && std::tolower(*cp, std::locale()) <= 'z') || + *cp == '_'); + + if (*cp == ')') + eaten = cp - str + 1; + } + } + + return eaten; + } + + /** \brief Tries to find and eat infinity representation. + * + * Searches for either "inf" or "infinity" sequence (case insensitive) + * to determine infinite floating point value representation. + * + * \param str character buffer to analyze. + * + * \return number of consumed characters. Naturally, if it's equal to zero, + * infinity representation wasn't found. + */ + template std::size_t eat_inf(const CharType* str) noexcept + { + std::size_t eaten(0); + + if ((str[0] == 'n' || str[0] == 'N') && (str[1] == 'f' || str[1] == 'F')) + { + const CharType* cp = str + 2; + eaten = 2; + + if (*cp == 'i' || *cp == 'I') + { + ++cp; + + const std::array suffix {{ 'n', 'i', 't', 'y' }}; + auto it = suffix.cbegin(); + + while (it != suffix.cend() && std::tolower(*cp, std::locale()) == *it) + { + ++cp; + ++it; + } + + if (it == suffix.cend()) + eaten = cp - str; + } + } + + return eaten; + } + + /** \brief Extracts up to \p **kappa** decimal digits from fraction part. + * + * Extracts decimal digits from fraction part and returns it as numerator + * value with denominator equal to \f$10^{\kappa}\f$. + * + * \tparam kappa maximum number of decimal digits to extract. + * \tparam FloatType destination type of floating point value to store the + * results. + * \tparam CharType character type (typically `char` or `wchar_t`) \p **str** + * consists of. + * + * \param str character buffer to extract from. + * + * \return Numerator value of the extracted decimal digits (i.e. as they + * are actually written after the decimal point). + */ + template + inline unsigned int extract_fraction_digits(const CharType* str) + { + static_assert(kappa <= std::numeric_limits::digits10, "Extracting values, exceeding 'int' capacity, is not supported."); + + std::array parsed_digits; + parsed_digits.fill(0); + + for (std::size_t pos = 0; pos < kappa; ++pos) + { + const auto c = str[pos] - '0'; + if (c >= 0 && c <= 9) + parsed_digits[pos] = c; + else + break; + } + + unsigned int result(0); + std::size_t pow(0); + for (auto rit = parsed_digits.rbegin(); rit != parsed_digits.rend(); ++rit) + result += (*rit) * seq_pow(pow++); + + return result; + } + + /** \brief Type of special value. */ + enum class speciality : unsigned char + { + /** \brief Normal value - no special. */ + no = 0, + /** \brief NaN (not a number) value. */ + nan, + /** \brief infinity value. */ + inf + }; + + /** \brief Return structure for `parse_digits`. + * + * \tparam FloatType destination type of floating point value to store the + * results. + * \tparam CharType character type (typically `char` or `wchar_t`) used. + */ + template struct digit_parse_result + { + /** \brief Pre-initializes members to sane values. */ + digit_parse_result() : value(), K(0), str_end(nullptr), frac(0), special(), sign(true) { } + + /** \brief Parsed mantissa value. */ + typename diy_fp::mantissa_storage_type value; + + /** \brief Decimal exponent, as calculated by exponent part and decimal + * point position. + */ + int K; + + /** \brief Pointer to the memory after the parsed part of the buffer. */ + const CharType* str_end; + + /** \brief Binary numerator of fractional part, to help correct rounding. */ + unsigned char frac; + + /** \brief Flag of special value possibly occured. */ + speciality special; + + /** \brief Sign of the value. */ + bool sign; + }; + + /** \brief Unified method to extract and parse digits in one pass. + * + * Goes through the string representation of the floating point value in + * the specified buffer, detects the meaning of each digit in its position + * and calculates main parts of floating point value — mantissa, exponent, + * sign, fractional part. + * + * \tparam kappa maximum number of digits to expect. + * \tparam calc_frac if `true`, try to calculate fractional part, if any. + * \tparam FloatType destination type of floating point value to store the + * results. + * \tparam CharType character type (typically `char` or `wchar_t`) \p **str** + * consists of. + * + * \param str Character buffer with floating point value representation to + * parse. + * + * \return `digit_parse_result` with the parsing results. + */ + template + inline digit_parse_result parse_digits(const CharType* str) noexcept + { + digit_parse_result ret; + + constexpr std::size_t kappa = decimal_q; + + std::vector parsed_digits; + parsed_digits.reserve(kappa); + + bool dot_set(false); + bool sign_set(false); + bool frac_calculated(false); + std::size_t pow_gain(0); + std::size_t zero_substring_length(0), fraction_digits_count(0); + + bool go_to_beach(false); + std::size_t pos(0); + + while(!go_to_beach) + { + const auto c = str[pos]; + switch (c) + { + case '0': + if (!parsed_digits.empty() || dot_set) + { + ++zero_substring_length; + pow_gain += !dot_set; + } + break; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (zero_substring_length && parsed_digits.size() < kappa) + { + const std::size_t spare_digits { kappa - parsed_digits.size() }; + auto zero_copy_count = zero_substring_length; + auto pow_gain_reduced = pow_gain; + + if (!parsed_digits.empty()) + { + zero_copy_count = std::min(zero_substring_length, spare_digits); + pow_gain_reduced = std::min(pow_gain, spare_digits); + + parsed_digits.insert(parsed_digits.end(), zero_copy_count, 0); + } + + fraction_digits_count += zero_copy_count - pow_gain_reduced; + zero_substring_length -= zero_copy_count; + pow_gain -= pow_gain_reduced; + } + + if (parsed_digits.size() < kappa) + { + parsed_digits.push_back(c - '0'); + fraction_digits_count += dot_set; + } + else + { + if (!frac_calculated) + { + const std::size_t frac_suffix_size = parsed_digits.size() + zero_substring_length - kappa; + auto tail = extract_fraction_digits(str + pos - frac_suffix_size); + ret.frac = convert_numerator(tail); + + frac_calculated = true; + } + + pow_gain += !dot_set; + } + break; + + case '.': + go_to_beach = dot_set; + dot_set = true; + break; + + case 'n': + case 'N': + if (pos == sign_set) + { + const std::size_t eaten = eat_nan(str + pos + 1); + pos += eaten + 1; + + if (eaten) + ret.special = speciality::nan; + } + + go_to_beach = true; + break; + + case 'i': + case 'I': + if (pos == sign_set) + { + const std::size_t eaten = eat_inf(str + pos + 1); + pos += eaten + 1; + + if (eaten) + ret.special = speciality::inf; + } + + go_to_beach = true; + break; + + case '-': + case '+': + if (pos == 0) + { + ret.sign = static_cast('-' - c); // '+' => true, '-' => false + sign_set = true; + break; + } + // fall through + + default: + go_to_beach = true; + break; + } + + go_to_beach |= pos > maximum_offset; + + ++pos; + } + + std::size_t pow(0); + for (auto rit = parsed_digits.rbegin(); rit != parsed_digits.rend(); ++rit) + ret.value += (*rit) * seq_pow::mantissa_storage_type, 10, decimal_q>(pow++); + + ret.str_end = str + (pos - 1); + ret.K = pow_gain - fraction_digits_count; + + return ret; + } + + /** \brief Return structure for `parse_mantissa`. + * + * \tparam FloatType destination value floating point type. + * \tparam CharType character type (typically `char` or `wchar_t`) used. + */ + template struct mantissa_parse_result + { + /** \brief Calculated mantissa value. */ + diy_fp value; + + /** \brief Corrected value of decimal exponent value. */ + int K; + + /** \brief Pointer to the memory after the parsed part of the buffer. */ + const CharType* str_end; + + /** \brief Flag of special value. */ + speciality special; + + /** \brief Sign of the value. */ + bool sign; + }; + + /** \brief Tides up results of `parse_digits` for **Krosh** to use. + * + * Packs mantissa value into `diy_fp` structure and performs the necessary + * rounding up according to the fractional part value. + * + * \tparam FloatType destination type of floating point value to store the + * results. + * \tparam CharType character type (typically `char` or `wchar_t`) \p **str** + * consists of. + * + * \param str Character buffer with floating point value representation to + * parse. + * + * \return `mantissa_parse_result` structure with the results of parsing + * and corrections. + */ + template inline mantissa_parse_result parse_mantissa(const CharType* str) + { + mantissa_parse_result ret; + + const auto& digits_parts(parse_digits(str)); + + ret.special = digits_parts.special; + ret.str_end = digits_parts.str_end; + ret.sign = digits_parts.sign; + + if (digits_parts.special == speciality::no) + { + ret.value = diy_fp(digits_parts.value, 0); + ret.K = digits_parts.K; + + if (digits_parts.value) + { + auto& w(ret.value); + w.normalize(); + + // extract additional binary digits and round up gently + if (digits_parts.frac) + { + assert(w.exponent() > (-1) * static_cast(fraction_binary_digits)); + const std::size_t lsb_pow(fraction_binary_digits + w.exponent()); + + typename diy_fp::mantissa_storage_type f(w.mantissa()); + f |= digits_parts.frac >> lsb_pow; + + w = diy_fp(f, w.exponent()); + + // round correctly avoiding integer overflow, undefined behaviour, pain and suffering + if (round_up(digits_parts.frac, lsb_pow).value) + { + ++w; + } + } + } + } + + return ret; + } + + /** \brief Return structure for `parse_exponent`. + * + * \tparam CharType character type (typically `char` or `wchar_t`) used. + */ + template struct exponent_parse_result + { + /** \brief Value of the exponent. */ + int value; + + /** \brief Pointer to the memory after the parsed part of the buffer. */ + const CharType* str_end; + }; + + /** \brief Parses exponent part of the floating point string representation. + * + * \tparam CharType character type (typically `char` or `wchar_t`) of \p **str**. + * + * \param str Exponent part of character buffer with floating point value + * representation to parse. + * + * \return `exponent_parse_result` structure with parse results. + */ + template inline exponent_parse_result parse_exponent(const CharType* str) + { + exponent_parse_result ret; + if (*str != 'e' && *str != 'E') + { + ret.value = 0; + ret.str_end = str; + } + else + { + ++str; + + const auto& digit_parts(parse_digits(str)); + + ret.value = digit_parts.value * seq_pow(digit_parts.K); + + if (!digit_parts.sign) + ret.value = -ret.value; + + ret.str_end = digit_parts.str_end; + } + + return ret; + } + + /** \brief Return structure, containing **Krosh** algorithm results. + * + * \tparam FloatType destination type of floating point value to store the + * results. + * \tparam CharType character type (typically `char` or `wchar_t`) used. + */ + template struct krosh_result + { + /** \brief The result floating point value, downsampled to the defined + * floating point type. + */ + FloatType value; + + /** \brief Pointer to the memory after the parsed part of the buffer. */ + const CharType* str_end; + + /** \brief Status of the performed conversion. */ + conversion_status status; + + /** \brief Flag indicating if the result ensured to be rounded correctly. */ + bool is_accurate; + }; + + /** \brief Implements **Krosh** algorithm. + * + * \tparam FloatType destination type of floating point value to store the + * results. + * + * \tparam CharType character type (typically `char` or `wchar_t`) \p **str** + * consists of. + * + * \param str Character buffer with floating point value + * representation to parse. + * + * \return `krosh_result` structure with all the results of **Krosh** + * algorithm. + */ + template krosh_result krosh(const CharType* str) + { + krosh_result ret; + + static_assert(sizeof(FloatType) <= sizeof(typename diy_fp::mantissa_storage_type), "Only floating point types no longer, than 64 bits are supported."); + + auto mp(parse_mantissa(str)); + + if (mp.special == speciality::no && mp.value.mantissa()) + { + diy_fp& w(mp.value); + + const auto& ep(parse_exponent(mp.str_end)); + + mp.K += ep.value; + + if (mp.K) + { + const bool b1 = mp.K >= powers_ten::boundaries.first; + const bool b2 = mp.K <= powers_ten::boundaries.second; + + if (b1 && b2) + { + w *= cached_power(mp.K); + } + else + { + if (!b1) + { + ret.value = FloatType(0); + ret.status = conversion_status::underflow; + } + else // !b2 + { + ret.value = huge_value(); + ret.status = conversion_status::overflow; + } + + ret.str_end = ep.str_end; + ret.is_accurate = true; + + return ret; + } + } + + w.normalize(); + const auto& v(w.downsample()); + ret.value = v.value; + ret.str_end = ep.str_end; + ret.is_accurate = v.is_accurate; + ret.status = v.status; + } + else + { + switch (mp.special) + { + case speciality::nan: + ret.value = std::numeric_limits::quiet_NaN(); + break; + + case speciality::inf: + ret.value = std::numeric_limits::infinity(); + break; + + default: + ret.value = 0; + break; + } + + ret.str_end = mp.str_end; + ret.is_accurate = true; + ret.status = conversion_status::success; + } + + if (!mp.sign) + ret.value = -ret.value; + + return ret; + } +} + +#endif // FLOAXIE_CROSH_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/memwrap.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/memwrap.h new file mode 100644 index 000000000..65d0c668e --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/memwrap.h @@ -0,0 +1,61 @@ +/* + * Copyright 2015, 2016 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLOAXIE_MEMWRAP_H +#define FLOAXIE_MEMWRAP_H + +#include +#include + +namespace floaxie +{ + namespace wrap + { + /** \brief wrapper to template `std::(w)memset` by character type. + * + * \tparam CharType character type used. + */ + template inline CharType* memset(CharType* dest, CharType ch, std::size_t count); + + template<> inline char* memset(char* dest, char ch, std::size_t count) + { + return static_cast(std::memset(dest, ch, count)); + } + + template<> inline wchar_t* memset(wchar_t* dest, wchar_t ch, std::size_t count) + { + return std::wmemset(dest, ch, count); + } + + /** \brief wrapper to template `std::(w)memmove` by character type. + * + * \tparam CharType character type used. + */ + template inline CharType* memmove(CharType* dest, const CharType* src, std::size_t count); + + template<> inline char* memmove(char* dest, const char* src, std::size_t count) + { + return static_cast(std::memmove(dest, src, count)); + } + + template<> inline wchar_t* memmove(wchar_t* dest, const wchar_t* src, std::size_t count) + { + return std::wmemmove(dest, src, count); + } + } +} + +#endif // FLOAXIE_MEMWRAP_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/powers_ten.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/powers_ten.h new file mode 100644 index 000000000..208ee1c11 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/powers_ten.h @@ -0,0 +1,47 @@ +/* + * Copyright 2015-2017 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * cached power literals and cached_power() function use code taken from + * Florian Loitsch's original Grisu algorithms implementation + * (http://florian.loitsch.com/publications/bench.tar.gz) + * and "Printing Floating-Point Numbers Quickly and Accurately with + * Integers" paper + * (http://florian.loitsch.com/publications/dtoa-pldi2010.pdf) + */ + +#ifndef FLOAXIE_POWERS_TEN_H +#define FLOAXIE_POWERS_TEN_H + +namespace floaxie +{ + /** \brief Structure template to store compile-time values of powers of ten. + * + * The template represents a structure family, which stores pre-calculated + * values of significands and exponents of powers of ten, represented in + * integer form of the specified precision. + * + * The exact values are written in specializations of the template for + * single precision or double precision floating point type. + * + * The template specializations are used in **Grisu** and **Krosh** algorithms + * to get the pre-calculated cached power of ten. + * + * \tparam FloatType floating point type, for which precision the values are + * calculated. + */ + template struct powers_ten; +} + +#endif // FLOAXIE_POWERS_TEN_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/powers_ten_double.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/powers_ten_double.h new file mode 100644 index 000000000..415ed0e15 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/powers_ten_double.h @@ -0,0 +1,419 @@ +/* + * Copyright 2015-2017 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * cached power literals and cached_power() function use code taken from + * Florian Loitsch's original Grisu algorithms implementation + * (http://florian.loitsch.com/publications/bench.tar.gz) + * and "Printing Floating-Point Numbers Quickly and Accurately with + * Integers" paper + * (http://florian.loitsch.com/publications/dtoa-pldi2010.pdf) + */ + +#ifndef FLOAXIE_POWERS_TEN_DOUBLE_H +#define FLOAXIE_POWERS_TEN_DOUBLE_H + +#include +#include + +#include + +namespace floaxie +{ + /** \brief Specialization of **powers_ten** for double precision + * floating point type (`double`). + * + * Significand (mantissa) type is `std::uint64_t`, exponent type is + * `int` (typically 32 bit, at least 16 bit). + * + * Values are calculated for powers of 10 in the range of [-343, 343] + * exponent. + */ + template<> struct powers_ten + { + /** \brief Pre-calculated binary 64-bit representation of mantissa of + * powers of 10 in the range of [-343, 343] for single precision + * floating point values. + */ + static constexpr std::uint64_t f[] = + { + 0xbf29dcaba82fdeae , 0xeef453d6923bd65a , 0x9558b4661b6565f8 , 0xbaaee17fa23ebf76 , + 0xe95a99df8ace6f54 , 0x91d8a02bb6c10594 , 0xb64ec836a47146fa , 0xe3e27a444d8d98b8 , + 0x8e6d8c6ab0787f73 , 0xb208ef855c969f50 , 0xde8b2b66b3bc4724 , 0x8b16fb203055ac76 , + 0xaddcb9e83c6b1794 , 0xd953e8624b85dd79 , 0x87d4713d6f33aa6c , 0xa9c98d8ccb009506 , + 0xd43bf0effdc0ba48 , 0x84a57695fe98746d , 0xa5ced43b7e3e9188 , 0xcf42894a5dce35ea , + 0x818995ce7aa0e1b2 , 0xa1ebfb4219491a1f , 0xca66fa129f9b60a7 , 0xfd00b897478238d1 , + 0x9e20735e8cb16382 , 0xc5a890362fddbc63 , 0xf712b443bbd52b7c , 0x9a6bb0aa55653b2d , + 0xc1069cd4eabe89f9 , 0xf148440a256e2c77 , 0x96cd2a865764dbca , 0xbc807527ed3e12bd , + 0xeba09271e88d976c , 0x93445b8731587ea3 , 0xb8157268fdae9e4c , 0xe61acf033d1a45df , + 0x8fd0c16206306bac , 0xb3c4f1ba87bc8697 , 0xe0b62e2929aba83c , 0x8c71dcd9ba0b4926 , + 0xaf8e5410288e1b6f , 0xdb71e91432b1a24b , 0x892731ac9faf056f , 0xab70fe17c79ac6ca , + 0xd64d3d9db981787d , 0x85f0468293f0eb4e , 0xa76c582338ed2622 , 0xd1476e2c07286faa , + 0x82cca4db847945ca , 0xa37fce126597973d , 0xcc5fc196fefd7d0c , 0xff77b1fcbebcdc4f , + 0x9faacf3df73609b1 , 0xc795830d75038c1e , 0xf97ae3d0d2446f25 , 0x9becce62836ac577 , + 0xc2e801fb244576d5 , 0xf3a20279ed56d48a , 0x9845418c345644d7 , 0xbe5691ef416bd60c , + 0xedec366b11c6cb8f , 0x94b3a202eb1c3f39 , 0xb9e08a83a5e34f08 , 0xe858ad248f5c22ca , + 0x91376c36d99995be , 0xb58547448ffffb2e , 0xe2e69915b3fff9f9 , 0x8dd01fad907ffc3c , + 0xb1442798f49ffb4b , 0xdd95317f31c7fa1d , 0x8a7d3eef7f1cfc52 , 0xad1c8eab5ee43b67 , + 0xd863b256369d4a41 , 0x873e4f75e2224e68 , 0xa90de3535aaae202 , 0xd3515c2831559a83 , + 0x8412d9991ed58092 , 0xa5178fff668ae0b6 , 0xce5d73ff402d98e4 , 0x80fa687f881c7f8e , + 0xa139029f6a239f72 , 0xc987434744ac874f , 0xfbe9141915d7a922 , 0x9d71ac8fada6c9b5 , + 0xc4ce17b399107c23 , 0xf6019da07f549b2b , 0x99c102844f94e0fb , 0xc0314325637a193a , + 0xf03d93eebc589f88 , 0x96267c7535b763b5 , 0xbbb01b9283253ca3 , 0xea9c227723ee8bcb , + 0x92a1958a7675175f , 0xb749faed14125d37 , 0xe51c79a85916f485 , 0x8f31cc0937ae58d3 , + 0xb2fe3f0b8599ef08 , 0xdfbdcece67006ac9 , 0x8bd6a141006042be , 0xaecc49914078536d , + 0xda7f5bf590966849 , 0x888f99797a5e012d , 0xaab37fd7d8f58179 , 0xd5605fcdcf32e1d7 , + 0x855c3be0a17fcd26 , 0xa6b34ad8c9dfc070 , 0xd0601d8efc57b08c , 0x823c12795db6ce57 , + 0xa2cb1717b52481ed , 0xcb7ddcdda26da269 , 0xfe5d54150b090b03 , 0x9efa548d26e5a6e2 , + 0xc6b8e9b0709f109a , 0xf867241c8cc6d4c1 , 0x9b407691d7fc44f8 , 0xc21094364dfb5637 , + 0xf294b943e17a2bc4 , 0x979cf3ca6cec5b5b , 0xbd8430bd08277231 , 0xece53cec4a314ebe , + 0x940f4613ae5ed137 , 0xb913179899f68584 , 0xe757dd7ec07426e5 , 0x9096ea6f3848984f , + 0xb4bca50b065abe63 , 0xe1ebce4dc7f16dfc , 0x8d3360f09cf6e4bd , 0xb080392cc4349ded , + 0xdca04777f541c568 , 0x89e42caaf9491b61 , 0xac5d37d5b79b6239 , 0xd77485cb25823ac7 , + 0x86a8d39ef77164bd , 0xa8530886b54dbdec , 0xd267caa862a12d67 , 0x8380dea93da4bc60 , + 0xa46116538d0deb78 , 0xcd795be870516656 , 0x806bd9714632dff6 , 0xa086cfcd97bf97f4 , + 0xc8a883c0fdaf7df0 , 0xfad2a4b13d1b5d6c , 0x9cc3a6eec6311a64 , 0xc3f490aa77bd60fd , + 0xf4f1b4d515acb93c , 0x991711052d8bf3c5 , 0xbf5cd54678eef0b7 , 0xef340a98172aace5 , + 0x9580869f0e7aac0f , 0xbae0a846d2195713 , 0xe998d258869facd7 , 0x91ff83775423cc06 , + 0xb67f6455292cbf08 , 0xe41f3d6a7377eeca , 0x8e938662882af53e , 0xb23867fb2a35b28e , + 0xdec681f9f4c31f31 , 0x8b3c113c38f9f37f , 0xae0b158b4738705f , 0xd98ddaee19068c76 , + 0x87f8a8d4cfa417ca , 0xa9f6d30a038d1dbc , 0xd47487cc8470652b , 0x84c8d4dfd2c63f3b , + 0xa5fb0a17c777cf0a , 0xcf79cc9db955c2cc , 0x81ac1fe293d599c0 , 0xa21727db38cb0030 , + 0xca9cf1d206fdc03c , 0xfd442e4688bd304b , 0x9e4a9cec15763e2f , 0xc5dd44271ad3cdba , + 0xf7549530e188c129 , 0x9a94dd3e8cf578ba , 0xc13a148e3032d6e8 , 0xf18899b1bc3f8ca2 , + 0x96f5600f15a7b7e5 , 0xbcb2b812db11a5de , 0xebdf661791d60f56 , 0x936b9fcebb25c996 , + 0xb84687c269ef3bfb , 0xe65829b3046b0afa , 0x8ff71a0fe2c2e6dc , 0xb3f4e093db73a093 , + 0xe0f218b8d25088b8 , 0x8c974f7383725573 , 0xafbd2350644eead0 , 0xdbac6c247d62a584 , + 0x894bc396ce5da772 , 0xab9eb47c81f5114f , 0xd686619ba27255a3 , 0x8613fd0145877586 , + 0xa798fc4196e952e7 , 0xd17f3b51fca3a7a1 , 0x82ef85133de648c5 , 0xa3ab66580d5fdaf6 , + 0xcc963fee10b7d1b3 , 0xffbbcfe994e5c620 , 0x9fd561f1fd0f9bd4 , 0xc7caba6e7c5382c9 , + 0xf9bd690a1b68637b , 0x9c1661a651213e2d , 0xc31bfa0fe5698db8 , 0xf3e2f893dec3f126 , + 0x986ddb5c6b3a76b8 , 0xbe89523386091466 , 0xee2ba6c0678b597f , 0x94db483840b717f0 , + 0xba121a4650e4ddec , 0xe896a0d7e51e1566 , 0x915e2486ef32cd60 , 0xb5b5ada8aaff80b8 , + 0xe3231912d5bf60e6 , 0x8df5efabc5979c90 , 0xb1736b96b6fd83b4 , 0xddd0467c64bce4a1 , + 0x8aa22c0dbef60ee4 , 0xad4ab7112eb3929e , 0xd89d64d57a607745 , 0x87625f056c7c4a8b , + 0xa93af6c6c79b5d2e , 0xd389b47879823479 , 0x843610cb4bf160cc , 0xa54394fe1eedb8ff , + 0xce947a3da6a9273e , 0x811ccc668829b887 , 0xa163ff802a3426a9 , 0xc9bcff6034c13053 , + 0xfc2c3f3841f17c68 , 0x9d9ba7832936edc1 , 0xc5029163f384a931 , 0xf64335bcf065d37d , + 0x99ea0196163fa42e , 0xc06481fb9bcf8d3a , 0xf07da27a82c37088 , 0x964e858c91ba2655 , + 0xbbe226efb628afeb , 0xeadab0aba3b2dbe5 , 0x92c8ae6b464fc96f , 0xb77ada0617e3bbcb , + 0xe55990879ddcaabe , 0x8f57fa54c2a9eab7 , 0xb32df8e9f3546564 , 0xdff9772470297ebd , + 0x8bfbea76c619ef36 , 0xaefae51477a06b04 , 0xdab99e59958885c5 , 0x88b402f7fd75539b , + 0xaae103b5fcd2a882 , 0xd59944a37c0752a2 , 0x857fcae62d8493a5 , 0xa6dfbd9fb8e5b88f , + 0xd097ad07a71f26b2 , 0x825ecc24c8737830 , 0xa2f67f2dfa90563b , 0xcbb41ef979346bca , + 0xfea126b7d78186bd , 0x9f24b832e6b0f436 , 0xc6ede63fa05d3144 , 0xf8a95fcf88747d94 , + 0x9b69dbe1b548ce7d , 0xc24452da229b021c , 0xf2d56790ab41c2a3 , 0x97c560ba6b0919a6 , + 0xbdb6b8e905cb600f , 0xed246723473e3813 , 0x9436c0760c86e30c , 0xb94470938fa89bcf , + 0xe7958cb87392c2c3 , 0x90bd77f3483bb9ba , 0xb4ecd5f01a4aa828 , 0xe2280b6c20dd5232 , + 0x8d590723948a535f , 0xb0af48ec79ace837 , 0xdcdb1b2798182245 , 0x8a08f0f8bf0f156b , + 0xac8b2d36eed2dac6 , 0xd7adf884aa879177 , 0x86ccbb52ea94baeb , 0xa87fea27a539e9a5 , + 0xd29fe4b18e88640f , 0x83a3eeeef9153e89 , 0xa48ceaaab75a8e2b , 0xcdb02555653131b6 , + 0x808e17555f3ebf12 , 0xa0b19d2ab70e6ed6 , 0xc8de047564d20a8c , 0xfb158592be068d2f , + 0x9ced737bb6c4183d , 0xc428d05aa4751e4d , 0xf53304714d9265e0 , 0x993fe2c6d07b7fac , + 0xbf8fdb78849a5f97 , 0xef73d256a5c0f77d , 0x95a8637627989aae , 0xbb127c53b17ec159 , + 0xe9d71b689dde71b0 , 0x9226712162ab070e , 0xb6b00d69bb55c8d1 , 0xe45c10c42a2b3b06 , + 0x8eb98a7a9a5b04e3 , 0xb267ed1940f1c61c , 0xdf01e85f912e37a3 , 0x8b61313bbabce2c6 , + 0xae397d8aa96c1b78 , 0xd9c7dced53c72256 , 0x881cea14545c7575 , 0xaa242499697392d3 , + 0xd4ad2dbfc3d07788 , 0x84ec3c97da624ab5 , 0xa6274bbdd0fadd62 , 0xcfb11ead453994ba , + 0x81ceb32c4b43fcf5 , 0xa2425ff75e14fc32 , 0xcad2f7f5359a3b3e , 0xfd87b5f28300ca0e , + 0x9e74d1b791e07e48 , 0xc612062576589ddb , 0xf79687aed3eec551 , 0x9abe14cd44753b53 , + 0xc16d9a0095928a27 , 0xf1c90080baf72cb1 , 0x971da05074da7bef , 0xbce5086492111aeb , + 0xec1e4a7db69561a5 , 0x9392ee8e921d5d07 , 0xb877aa3236a4b449 , 0xe69594bec44de15b , + 0x901d7cf73ab0acd9 , 0xb424dc35095cd80f , 0xe12e13424bb40e13 , 0x8cbccc096f5088cc , + 0xafebff0bcb24aaff , 0xdbe6fecebdedd5bf , 0x89705f4136b4a597 , 0xabcc77118461cefd , + 0xd6bf94d5e57a42bc , 0x8637bd05af6c69b6 , 0xa7c5ac471b478423 , 0xd1b71758e219652c , + 0x83126e978d4fdf3b , 0xa3d70a3d70a3d70a , 0xcccccccccccccccd , 0x8000000000000000 , + 0xa000000000000000 , 0xc800000000000000 , 0xfa00000000000000 , 0x9c40000000000000 , + 0xc350000000000000 , 0xf424000000000000 , 0x9896800000000000 , 0xbebc200000000000 , + 0xee6b280000000000 , 0x9502f90000000000 , 0xba43b74000000000 , 0xe8d4a51000000000 , + 0x9184e72a00000000 , 0xb5e620f480000000 , 0xe35fa931a0000000 , 0x8e1bc9bf04000000 , + 0xb1a2bc2ec5000000 , 0xde0b6b3a76400000 , 0x8ac7230489e80000 , 0xad78ebc5ac620000 , + 0xd8d726b7177a8000 , 0x878678326eac9000 , 0xa968163f0a57b400 , 0xd3c21bcecceda100 , + 0x84595161401484a0 , 0xa56fa5b99019a5c8 , 0xcecb8f27f4200f3a , 0x813f3978f8940984 , + 0xa18f07d736b90be5 , 0xc9f2c9cd04674edf , 0xfc6f7c4045812296 , 0x9dc5ada82b70b59e , + 0xc5371912364ce305 , 0xf684df56c3e01bc7 , 0x9a130b963a6c115c , 0xc097ce7bc90715b3 , + 0xf0bdc21abb48db20 , 0x96769950b50d88f4 , 0xbc143fa4e250eb31 , 0xeb194f8e1ae525fd , + 0x92efd1b8d0cf37be , 0xb7abc627050305ae , 0xe596b7b0c643c719 , 0x8f7e32ce7bea5c70 , + 0xb35dbf821ae4f38c , 0xe0352f62a19e306f , 0x8c213d9da502de45 , 0xaf298d050e4395d7 , + 0xdaf3f04651d47b4c , 0x88d8762bf324cd10 , 0xab0e93b6efee0054 , 0xd5d238a4abe98068 , + 0x85a36366eb71f041 , 0xa70c3c40a64e6c52 , 0xd0cf4b50cfe20766 , 0x82818f1281ed44a0 , + 0xa321f2d7226895c8 , 0xcbea6f8ceb02bb3a , 0xfee50b7025c36a08 , 0x9f4f2726179a2245 , + 0xc722f0ef9d80aad6 , 0xf8ebad2b84e0d58c , 0x9b934c3b330c8577 , 0xc2781f49ffcfa6d5 , + 0xf316271c7fc3908b , 0x97edd871cfda3a57 , 0xbde94e8e43d0c8ec , 0xed63a231d4c4fb27 , + 0x945e455f24fb1cf9 , 0xb975d6b6ee39e437 , 0xe7d34c64a9c85d44 , 0x90e40fbeea1d3a4b , + 0xb51d13aea4a488dd , 0xe264589a4dcdab15 , 0x8d7eb76070a08aed , 0xb0de65388cc8ada8 , + 0xdd15fe86affad912 , 0x8a2dbf142dfcc7ab , 0xacb92ed9397bf996 , 0xd7e77a8f87daf7fc , + 0x86f0ac99b4e8dafd , 0xa8acd7c0222311bd , 0xd2d80db02aabd62c , 0x83c7088e1aab65db , + 0xa4b8cab1a1563f52 , 0xcde6fd5e09abcf27 , 0x80b05e5ac60b6178 , 0xa0dc75f1778e39d6 , + 0xc913936dd571c84c , 0xfb5878494ace3a5f , 0x9d174b2dcec0e47b , 0xc45d1df942711d9a , + 0xf5746577930d6501 , 0x9968bf6abbe85f20 , 0xbfc2ef456ae276e9 , 0xefb3ab16c59b14a3 , + 0x95d04aee3b80ece6 , 0xbb445da9ca61281f , 0xea1575143cf97227 , 0x924d692ca61be758 , + 0xb6e0c377cfa2e12e , 0xe498f455c38b997a , 0x8edf98b59a373fec , 0xb2977ee300c50fe7 , + 0xdf3d5e9bc0f653e1 , 0x8b865b215899f46d , 0xae67f1e9aec07188 , 0xda01ee641a708dea , + 0x884134fe908658b2 , 0xaa51823e34a7eedf , 0xd4e5e2cdc1d1ea96 , 0x850fadc09923329e , + 0xa6539930bf6bff46 , 0xcfe87f7cef46ff17 , 0x81f14fae158c5f6e , 0xa26da3999aef774a , + 0xcb090c8001ab551c , 0xfdcb4fa002162a63 , 0x9e9f11c4014dda7e , 0xc646d63501a1511e , + 0xf7d88bc24209a565 , 0x9ae757596946075f , 0xc1a12d2fc3978937 , 0xf209787bb47d6b85 , + 0x9745eb4d50ce6333 , 0xbd176620a501fc00 , 0xec5d3fa8ce427b00 , 0x93ba47c980e98ce0 , + 0xb8a8d9bbe123f018 , 0xe6d3102ad96cec1e , 0x9043ea1ac7e41393 , 0xb454e4a179dd1877 , + 0xe16a1dc9d8545e95 , 0x8ce2529e2734bb1d , 0xb01ae745b101e9e4 , 0xdc21a1171d42645d , + 0x899504ae72497eba , 0xabfa45da0edbde69 , 0xd6f8d7509292d603 , 0x865b86925b9bc5c2 , + 0xa7f26836f282b733 , 0xd1ef0244af2364ff , 0x8335616aed761f1f , 0xa402b9c5a8d3a6e7 , + 0xcd036837130890a1 , 0x802221226be55a65 , 0xa02aa96b06deb0fe , 0xc83553c5c8965d3d , + 0xfa42a8b73abbf48d , 0x9c69a97284b578d8 , 0xc38413cf25e2d70e , 0xf46518c2ef5b8cd1 , + 0x98bf2f79d5993803 , 0xbeeefb584aff8604 , 0xeeaaba2e5dbf6785 , 0x952ab45cfa97a0b3 , + 0xba756174393d88e0 , 0xe912b9d1478ceb17 , 0x91abb422ccb812ef , 0xb616a12b7fe617aa , + 0xe39c49765fdf9d95 , 0x8e41ade9fbebc27d , 0xb1d219647ae6b31c , 0xde469fbd99a05fe3 , + 0x8aec23d680043bee , 0xada72ccc20054aea , 0xd910f7ff28069da4 , 0x87aa9aff79042287 , + 0xa99541bf57452b28 , 0xd3fa922f2d1675f2 , 0x847c9b5d7c2e09b7 , 0xa59bc234db398c25 , + 0xcf02b2c21207ef2f , 0x8161afb94b44f57d , 0xa1ba1ba79e1632dc , 0xca28a291859bbf93 , + 0xfcb2cb35e702af78 , 0x9defbf01b061adab , 0xc56baec21c7a1916 , 0xf6c69a72a3989f5c , + 0x9a3c2087a63f6399 , 0xc0cb28a98fcf3c80 , 0xf0fdf2d3f3c30b9f , 0x969eb7c47859e744 , + 0xbc4665b596706115 , 0xeb57ff22fc0c795a , 0x9316ff75dd87cbd8 , 0xb7dcbf5354e9bece , + 0xe5d3ef282a242e82 , 0x8fa475791a569d11 , 0xb38d92d760ec4455 , 0xe070f78d3927556b , + 0x8c469ab843b89563 , 0xaf58416654a6babb , 0xdb2e51bfe9d0696a , 0x88fcf317f22241e2 , + 0xab3c2fddeeaad25b , 0xd60b3bd56a5586f2 , 0x85c7056562757457 , 0xa738c6bebb12d16d , + 0xd106f86e69d785c8 , 0x82a45b450226b39d , 0xa34d721642b06084 , 0xcc20ce9bd35c78a5 , + 0xff290242c83396ce , 0x9f79a169bd203e41 , 0xc75809c42c684dd1 , 0xf92e0c3537826146 , + 0x9bbcc7a142b17ccc , 0xc2abf989935ddbfe , 0xf356f7ebf83552fe , 0x98165af37b2153df , + 0xbe1bf1b059e9a8d6 , 0xeda2ee1c7064130c , 0x9485d4d1c63e8be8 , 0xb9a74a0637ce2ee1 , + 0xe8111c87c5c1ba9a , 0x910ab1d4db9914a0 , 0xb54d5e4a127f59c8 , 0xe2a0b5dc971f303a , + 0x8da471a9de737e24 , 0xb10d8e1456105dad , 0xdd50f1996b947519 , 0x8a5296ffe33cc930 , + 0xace73cbfdc0bfb7b , 0xd8210befd30efa5a , 0x8714a775e3e95c78 , 0xa8d9d1535ce3b396 , + 0xd31045a8341ca07c , 0x83ea2b892091e44e , 0xa4e4b66b68b65d61 , 0xce1de40642e3f4b9 , + 0x80d2ae83e9ce78f4 , 0xa1075a24e4421731 , 0xc94930ae1d529cfd , 0xfb9b7cd9a4a7443c , + 0x9d412e0806e88aa6 , 0xc491798a08a2ad4f , 0xf5b5d7ec8acb58a3 , 0x9991a6f3d6bf1766 , + 0xbff610b0cc6edd3f , 0xeff394dcff8a948f , 0x95f83d0a1fb69cd9 , 0xbb764c4ca7a44410 , + 0xea53df5fd18d5514 , 0x92746b9be2f8552c , 0xb7118682dbb66a77 , 0xe4d5e82392a40515 , + 0x8f05b1163ba6832d , 0xb2c71d5bca9023f8 , 0xdf78e4b2bd342cf7 , 0x8bab8eefb6409c1a , + 0xae9672aba3d0c321 , 0xda3c0f568cc4f3e9 , 0x8865899617fb1871 , 0xaa7eebfb9df9de8e , + 0xd51ea6fa85785631 , 0x8533285c936b35df , 0xa67ff273b8460357 , 0xd01fef10a657842c , + 0x8213f56a67f6b29c , 0xa298f2c501f45f43 , 0xcb3f2f7642717713 , 0xfe0efb53d30dd4d8 , + 0x9ec95d1463e8a507 , 0xc67bb4597ce2ce49 , 0xf81aa16fdc1b81db , 0x9b10a4e5e9913129 , + 0xc1d4ce1f63f57d73 , 0xf24a01a73cf2dcd0 , 0x976e41088617ca02 , 0xbd49d14aa79dbc82 , + 0xec9c459d51852ba3 , 0x93e1ab8252f33b46 , 0xb8da1662e7b00a17 , 0xe7109bfba19c0c9d , + 0x906a617d450187e2 , 0xb484f9dc9641e9db , 0xe1a63853bbd26451 , 0x8d07e33455637eb3 , + 0xb049dc016abc5e60 , 0xdc5c5301c56b75f7 , 0x89b9b3e11b6329bb , 0xac2820d9623bf429 , + 0xd732290fbacaf134 , 0x867f59a9d4bed6c0 , 0xa81f301449ee8c70 , 0xd226fc195c6a2f8c , + 0x83585d8fd9c25db8 , 0xa42e74f3d032f526 , 0xcd3a1230c43fb26f , 0x80444b5e7aa7cf85 , + 0xa0555e361951c367 , 0xc86ab5c39fa63441 , 0xfa856334878fc151 , 0x9c935e00d4b9d8d2 , + 0xc3b8358109e84f07 , 0xf4a642e14c6262c9 , 0x98e7e9cccfbd7dbe , 0xbf21e44003acdd2d , + 0xeeea5d5004981478 , 0x95527a5202df0ccb , 0xbaa718e68396cffe , 0xe950df20247c83fd , + 0x91d28b7416cdd27e , 0xb6472e511c81471e , 0xe3d8f9e563a198e5 , 0x8e679c2f5e44ff8f , + 0xb201833b35d63f73 , 0xde81e40a034bcf50 , 0x8b112e86420f6192 , 0xadd57a27d29339f6 , + 0xd94ad8b1c7380874 , 0x87cec76f1c830549 , 0xa9c2794ae3a3c69b , 0xd433179d9c8cb841 , + 0x849feec281d7f329 , 0xa5c7ea73224deff3 , 0xcf39e50feae16bf0 , 0x81842f29f2cce376 , + 0xa1e53af46f801c53 , 0xca5e89b18b602368 , 0xfcf62c1dee382c42 , 0x9e19db92b4e31ba9 , + 0xc5a05277621be294 , 0xf70867153aa2db39 , 0x9a65406d44a5c903 , 0xc0fe908895cf3b44 , + 0xf13e34aabb430a15 , 0x96c6e0eab509e64d , 0xbc789925624c5fe1 , 0xeb96bf6ebadf77d9 , + 0x933e37a534cbaae8 , 0xb80dc58e81fe95a1 , 0xe61136f2227e3b0a , 0x8fcac257558ee4e6 , + 0xb3bd72ed2af29e20 , 0xe0accfa875af45a8 , 0x8c6c01c9498d8b89 , 0xaf87023b9bf0ee6b , + 0xdb68c2ca82ed2a06 , 0x892179be91d43a44 , 0xab69d82e364948d4 + }; + + /** \brief Pre-calculated values of binary exponent of powers of 10 in the + * range of [-343, 343]. + */ + static constexpr int e[] = + { + -1203 , -1200 , -1196 , -1193 , + -1190 , -1186 , -1183 , -1180 , + -1176 , -1173 , -1170 , -1166 , + -1163 , -1160 , -1156 , -1153 , + -1150 , -1146 , -1143 , -1140 , + -1136 , -1133 , -1130 , -1127 , + -1123 , -1120 , -1117 , -1113 , + -1110 , -1107 , -1103 , -1100 , + -1097 , -1093 , -1090 , -1087 , + -1083 , -1080 , -1077 , -1073 , + -1070 , -1067 , -1063 , -1060 , + -1057 , -1053 , -1050 , -1047 , + -1043 , -1040 , -1037 , -1034 , + -1030 , -1027 , -1024 , -1020 , + -1017 , -1014 , -1010 , -1007 , + -1004 , -1000 , -997 , -994 , + -990 , -987 , -984 , -980 , + -977 , -974 , -970 , -967 , + -964 , -960 , -957 , -954 , + -950 , -947 , -944 , -940 , + -937 , -934 , -931 , -927 , + -924 , -921 , -917 , -914 , + -911 , -907 , -904 , -901 , + -897 , -894 , -891 , -887 , + -884 , -881 , -877 , -874 , + -871 , -867 , -864 , -861 , + -857 , -854 , -851 , -847 , + -844 , -841 , -838 , -834 , + -831 , -828 , -824 , -821 , + -818 , -814 , -811 , -808 , + -804 , -801 , -798 , -794 , + -791 , -788 , -784 , -781 , + -778 , -774 , -771 , -768 , + -764 , -761 , -758 , -754 , + -751 , -748 , -744 , -741 , + -738 , -735 , -731 , -728 , + -725 , -721 , -718 , -715 , + -711 , -708 , -705 , -701 , + -698 , -695 , -691 , -688 , + -685 , -681 , -678 , -675 , + -671 , -668 , -665 , -661 , + -658 , -655 , -651 , -648 , + -645 , -642 , -638 , -635 , + -632 , -628 , -625 , -622 , + -618 , -615 , -612 , -608 , + -605 , -602 , -598 , -595 , + -592 , -588 , -585 , -582 , + -578 , -575 , -572 , -568 , + -565 , -562 , -558 , -555 , + -552 , -549 , -545 , -542 , + -539 , -535 , -532 , -529 , + -525 , -522 , -519 , -515 , + -512 , -509 , -505 , -502 , + -499 , -495 , -492 , -489 , + -485 , -482 , -479 , -475 , + -472 , -469 , -465 , -462 , + -459 , -455 , -452 , -449 , + -446 , -442 , -439 , -436 , + -432 , -429 , -426 , -422 , + -419 , -416 , -412 , -409 , + -406 , -402 , -399 , -396 , + -392 , -389 , -386 , -382 , + -379 , -376 , -372 , -369 , + -366 , -362 , -359 , -356 , + -353 , -349 , -346 , -343 , + -339 , -336 , -333 , -329 , + -326 , -323 , -319 , -316 , + -313 , -309 , -306 , -303 , + -299 , -296 , -293 , -289 , + -286 , -283 , -279 , -276 , + -273 , -269 , -266 , -263 , + -259 , -256 , -253 , -250 , + -246 , -243 , -240 , -236 , + -233 , -230 , -226 , -223 , + -220 , -216 , -213 , -210 , + -206 , -203 , -200 , -196 , + -193 , -190 , -186 , -183 , + -180 , -176 , -173 , -170 , + -166 , -163 , -160 , -157 , + -153 , -150 , -147 , -143 , + -140 , -137 , -133 , -130 , + -127 , -123 , -120 , -117 , + -113 , -110 , -107 , -103 , + -100 , -97 , -93 , -90 , + -87 , -83 , -80 , -77 , + -73 , -70 , -67 , -63 , + -60 , -57 , -54 , -50 , + -47 , -44 , -40 , -37 , + -34 , -30 , -27 , -24 , + -20 , -17 , -14 , -10 , + -7 , -4 , 0 , 3 , + 6 , 10 , 13 , 16 , + 20 , 23 , 26 , 30 , + 33 , 36 , 39 , 43 , + 46 , 49 , 53 , 56 , + 59 , 63 , 66 , 69 , + 73 , 76 , 79 , 83 , + 86 , 89 , 93 , 96 , + 99 , 103 , 106 , 109 , + 113 , 116 , 119 , 123 , + 126 , 129 , 132 , 136 , + 139 , 142 , 146 , 149 , + 152 , 156 , 159 , 162 , + 166 , 169 , 172 , 176 , + 179 , 182 , 186 , 189 , + 192 , 196 , 199 , 202 , + 206 , 209 , 212 , 216 , + 219 , 222 , 226 , 229 , + 232 , 235 , 239 , 242 , + 245 , 249 , 252 , 255 , + 259 , 262 , 265 , 269 , + 272 , 275 , 279 , 282 , + 285 , 289 , 292 , 295 , + 299 , 302 , 305 , 309 , + 312 , 315 , 319 , 322 , + 325 , 328 , 332 , 335 , + 338 , 342 , 345 , 348 , + 352 , 355 , 358 , 362 , + 365 , 368 , 372 , 375 , + 378 , 382 , 385 , 388 , + 392 , 395 , 398 , 402 , + 405 , 408 , 412 , 415 , + 418 , 422 , 425 , 428 , + 431 , 435 , 438 , 441 , + 445 , 448 , 451 , 455 , + 458 , 461 , 465 , 468 , + 471 , 475 , 478 , 481 , + 485 , 488 , 491 , 495 , + 498 , 501 , 505 , 508 , + 511 , 515 , 518 , 521 , + 524 , 528 , 531 , 534 , + 538 , 541 , 544 , 548 , + 551 , 554 , 558 , 561 , + 564 , 568 , 571 , 574 , + 578 , 581 , 584 , 588 , + 591 , 594 , 598 , 601 , + 604 , 608 , 611 , 614 , + 617 , 621 , 624 , 627 , + 631 , 634 , 637 , 641 , + 644 , 647 , 651 , 654 , + 657 , 661 , 664 , 667 , + 671 , 674 , 677 , 681 , + 684 , 687 , 691 , 694 , + 697 , 701 , 704 , 707 , + 711 , 714 , 717 , 720 , + 724 , 727 , 730 , 734 , + 737 , 740 , 744 , 747 , + 750 , 754 , 757 , 760 , + 764 , 767 , 770 , 774 , + 777 , 780 , 784 , 787 , + 790 , 794 , 797 , 800 , + 804 , 807 , 810 , 813 , + 817 , 820 , 823 , 827 , + 830 , 833 , 837 , 840 , + 843 , 847 , 850 , 853 , + 857 , 860 , 863 , 867 , + 870 , 873 , 877 , 880 , + 883 , 887 , 890 , 893 , + 897 , 900 , 903 , 907 , + 910 , 913 , 916 , 920 , + 923 , 926 , 930 , 933 , + 936 , 940 , 943 , 946 , + 950 , 953 , 956 , 960 , + 963 , 966 , 970 , 973 , + 976 , 980 , 983 , 986 , + 990 , 993 , 996 , 1000 , + 1003 , 1006 , 1009 , 1013 , + 1016 , 1019 , 1023 , 1026 , + 1029 , 1033 , 1036 , 1039 , + 1043 , 1046 , 1049 , 1053 , + 1056 , 1059 , 1063 , 1066 , + 1069 , 1073 , 1076 + }; + + /** \brief Offsef of the values for zero power in the arrays. + */ + static constexpr std::size_t pow_0_offset = 343; + + /** \brief Boundaries of possible powers of ten for the type. + */ + static constexpr std::pair boundaries = { -343, 343 }; + }; + + constexpr decltype(powers_ten::f) powers_ten::f; + constexpr decltype(powers_ten::e) powers_ten::e; + constexpr std::size_t powers_ten::pow_0_offset; + constexpr std::pair powers_ten::boundaries; +} + +#endif // FLOAXIE_POWERS_TEN_DOUBLE_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/powers_ten_single.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/powers_ten_single.h new file mode 100644 index 000000000..86a91bc81 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/powers_ten_single.h @@ -0,0 +1,276 @@ +/* + * Copyright 2015-2017 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * cached power literals and cached_power() function use code taken from + * Florian Loitsch's original Grisu algorithms implementation + * (http://florian.loitsch.com/publications/bench.tar.gz) + * and "Printing Floating-Point Numbers Quickly and Accurately with + * Integers" paper + * (http://florian.loitsch.com/publications/dtoa-pldi2010.pdf) + */ + +#ifndef FLOAXIE_POWERS_TEN_SINGLE_H +#define FLOAXIE_POWERS_TEN_SINGLE_H + +#include + +#include + +namespace floaxie +{ + /** \brief Specialization of **powers_ten** for single precision + * floating point type (`float`). + * + * Significand (mantissa) type is `std::uint32_t`, exponent type is + * `int` (typically 32 bit, at least 16 bit). + * + * Values are calculated for powers of 10 in the range of [-50, 50] + * exponent. + */ + template<> struct powers_ten + { + /** \brief Pre-calculated binary 32-bit representation of mantissa of + * powers of 10 in the range of [-50, 50] for single precision + * floating point values. + */ + static constexpr std::uint32_t f[] = + { + 0xef73d257, + 0x95a86376, + 0xbb127c54, + 0xe9d71b69, + 0x92267121, + 0xb6b00d6a, + 0xe45c10c4, + 0x8eb98a7b, + 0xb267ed19, + 0xdf01e860, + 0x8b61313c, + 0xae397d8b, + 0xd9c7dced, + 0x881cea14, + 0xaa242499, + 0xd4ad2dc0, + 0x84ec3c98, + 0xa6274bbe, + 0xcfb11ead, + 0x81ceb32c, + 0xa2425ff7, + 0xcad2f7f5, + 0xfd87b5f3, + 0x9e74d1b8, + 0xc6120625, + 0xf79687af, + 0x9abe14cd, + 0xc16d9a01, + 0xf1c90081, + 0x971da050, + 0xbce50865, + 0xec1e4a7e, + 0x9392ee8f, + 0xb877aa32, + 0xe69594bf, + 0x901d7cf7, + 0xb424dc35, + 0xe12e1342, + 0x8cbccc09, + 0xafebff0c, + 0xdbe6fecf, + 0x89705f41, + 0xabcc7712, + 0xd6bf94d6, + 0x8637bd06, + 0xa7c5ac47, + 0xd1b71759, + 0x83126e98, + 0xa3d70a3d, + 0xcccccccd, + 0x80000000, + 0xa0000000, + 0xc8000000, + 0xfa000000, + 0x9c400000, + 0xc3500000, + 0xf4240000, + 0x98968000, + 0xbebc2000, + 0xee6b2800, + 0x9502f900, + 0xba43b740, + 0xe8d4a510, + 0x9184e72a, + 0xb5e620f4, + 0xe35fa932, + 0x8e1bc9bf, + 0xb1a2bc2f, + 0xde0b6b3a, + 0x8ac72305, + 0xad78ebc6, + 0xd8d726b7, + 0x87867832, + 0xa968163f, + 0xd3c21bcf, + 0x84595161, + 0xa56fa5ba, + 0xcecb8f28, + 0x813f3979, + 0xa18f07d7, + 0xc9f2c9cd, + 0xfc6f7c40, + 0x9dc5ada8, + 0xc5371912, + 0xf684df57, + 0x9a130b96, + 0xc097ce7c, + 0xf0bdc21b, + 0x96769951, + 0xbc143fa5, + 0xeb194f8e, + 0x92efd1b9, + 0xb7abc627, + 0xe596b7b1, + 0x8f7e32ce, + 0xb35dbf82, + 0xe0352f63, + 0x8c213d9e, + 0xaf298d05, + 0xdaf3f046, + 0x88d8762c + }; + + /** \brief Pre-calculated values of binary exponent of powers of 10 in the + * range of [-50, 50]. + */ + static constexpr int e[] = + { + -198, + -194, + -191, + -188, + -184, + -181, + -178, + -174, + -171, + -168, + -164, + -161, + -158, + -154, + -151, + -148, + -144, + -141, + -138, + -134, + -131, + -128, + -125, + -121, + -118, + -115, + -111, + -108, + -105, + -101, + -98, + -95, + -91, + -88, + -85, + -81, + -78, + -75, + -71, + -68, + -65, + -61, + -58, + -55, + -51, + -48, + -45, + -41, + -38, + -35, + -31, + -28, + -25, + -22, + -18, + -15, + -12, + -8, + -5, + -2, + 2, + 5, + 8, + 12, + 15, + 18, + 22, + 25, + 28, + 32, + 35, + 38, + 42, + 45, + 48, + 52, + 55, + 58, + 62, + 65, + 68, + 71, + 75, + 78, + 81, + 85, + 88, + 91, + 95, + 98, + 101, + 105, + 108, + 111, + 115, + 118, + 121, + 125, + 128, + 131, + 135 + }; + + /** \brief Offsef of the values for zero power in the arrays. + */ + static constexpr std::size_t pow_0_offset = 50; + + /** \brief Boundaries of possible powers of ten for the type. + */ + static constexpr std::pair boundaries = { -50, 50 }; + }; + + constexpr decltype(powers_ten::f) powers_ten::f; + constexpr decltype(powers_ten::e) powers_ten::e; + constexpr std::size_t powers_ten::pow_0_offset; + constexpr std::pair powers_ten::boundaries; +} + +#endif // FLOAXIE_POWERS_TEN_SINGLE_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/prettify.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/prettify.h new file mode 100644 index 000000000..8cf1a809d --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/prettify.h @@ -0,0 +1,221 @@ +/* + * Copyright 2015-2022 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * prettify_string() and fill_exponent() functions use code taken from + * Florian Loitsch's original Grisu algorithms implementation + * (http://florian.loitsch.com/publications/bench.tar.gz) + * and "Printing Floating-Point Numbers Quickly and Accurately with + * Integers" paper + * (http://florian.loitsch.com/publications/dtoa-pldi2010.pdf) + */ + +#ifndef FLOAXIE_PRETTIFY_H +#define FLOAXIE_PRETTIFY_H + +#include +#include +#include +#include + +#include +#include +#include + +namespace floaxie +{ + /** \brief Format enumeration. + * + */ + enum format { + /** \brief Decimal format. */ + decimal, + /** \brief Decimal exponent (a.k.a. *scientific*) format. */ + scientific + }; + + /** \brief LUT of two-digit decimal values to speed up their printing. */ + constexpr const char digits_lut[200] = { + '0', '0', '0', '1', '0', '2', '0', '3', '0', '4', '0', '5', '0', '6', '0', '7', '0', '8', '0', '9', + '1', '0', '1', '1', '1', '2', '1', '3', '1', '4', '1', '5', '1', '6', '1', '7', '1', '8', '1', '9', + '2', '0', '2', '1', '2', '2', '2', '3', '2', '4', '2', '5', '2', '6', '2', '7', '2', '8', '2', '9', + '3', '0', '3', '1', '3', '2', '3', '3', '3', '4', '3', '5', '3', '6', '3', '7', '3', '8', '3', '9', + '4', '0', '4', '1', '4', '2', '4', '3', '4', '4', '4', '5', '4', '6', '4', '7', '4', '8', '4', '9', + '5', '0', '5', '1', '5', '2', '5', '3', '5', '4', '5', '5', '5', '6', '5', '7', '5', '8', '5', '9', + '6', '0', '6', '1', '6', '2', '6', '3', '6', '4', '6', '5', '6', '6', '6', '7', '6', '8', '6', '9', + '7', '0', '7', '1', '7', '2', '7', '3', '7', '4', '7', '5', '7', '6', '7', '7', '7', '8', '7', '9', + '8', '0', '8', '1', '8', '2', '8', '3', '8', '4', '8', '5', '8', '6', '8', '7', '8', '8', '8', '9', + '9', '0', '9', '1', '9', '2', '9', '3', '9', '4', '9', '5', '9', '6', '9', '7', '9', '8', '9', '9' + }; + + /** \brief Detects the more appropriate format to print based on + * \p **threshold** value. + * \tparam threshold the maximum number of digits in the string + * representation, when decimal format can be used (otherwise decimal + * exponent or *scientific* format is used). + * + * \return value of the chosen format. + * \see `format` + */ + template inline format choose_format(const std::size_t field_width) noexcept + { + static_assert(threshold > static_pow<10, 1>(), "Only 10 ⩽ |threshold| ⩽ 100 is supported"); + + return field_width > threshold ? format::scientific : format::decimal; + } + + /** \brief Prints decimal exponent value. + * + * \tparam CharType character type (typically `char` or `wchar_t`) of the + * output buffer \p **buffer**. + * + * \param K decimal exponent value. + * \param buffer character buffer to print to. + * + * \return number of characters written to the buffer. + */ + template inline std::size_t fill_exponent(unsigned int K, CharType* buffer) noexcept + { + const unsigned char hundreds = static_cast(K / 100); + K %= 100; + buffer[0] = '0' + hundreds; + buffer += (hundreds > 0); + + const char* d = digits_lut + K * 2; + buffer[0] = d[0]; + buffer[1] = d[1]; + + buffer[2] = '\0'; + + return 2 + (hundreds > 0); + } + + /** \brief Prints exponent (*scientific*) part of value representation in + * decimal exponent format. + * + * \tparam CharType character type (typically `char` or `wchar_t`) of the + * output buffer \p **buffer**. + * + * \param buffer character buffer with properly printed mantissa. + * \param len output parameter to return the length of printed + * representation. + * \param dot_pos number of character, where dot position should be placed. + * + * \return number of characters written to the buffer. + * + * \see `print_decimal()` + */ + template inline std::size_t print_scientific(CharType* buffer, const unsigned int len, const int dot_pos) noexcept + { + const int K = dot_pos - 1; + if (len > 1) + { + /* leave the first digit. then add a '.' and at the end 'e...' */ + wrap::memmove(buffer + 2, buffer + 1, len - 1); + buffer[1] = '.'; + buffer += len; + } + + /* add 'e...' */ + buffer[1] = 'e'; + buffer[2] = '-'; + buffer += K < 0; + + return len + /*dot*/(len > 1) + /*'e'*/1 + /*exp sign*/(K < 0) + fill_exponent(std::abs(K), buffer + 2); + } + + /** \brief Formats decimal mantissa part of value representation. + * + * Tides up the printed digits in \p **buffer**, adding leading zeros, + * placing the decimal point into the proper place etc. + * + * \tparam CharType character type (typically `char` or `wchar_t`) of the + * output buffer \p **buffer**. + * + * \param buffer character buffer with printed digits. + * \param len length of current representation in \p **buffer**. + * \param k decimal exponent of the value. + * + * \return number of characters written to the buffer. + */ + template inline std::size_t print_decimal(CharType* buffer, const unsigned int len, const int k) noexcept + { + const int dot_pos = static_cast(len) + k; + + const unsigned int actual_dot_pos = dot_pos > 0 ? uint32_t(dot_pos) : 1; + + const unsigned int left_offset = dot_pos > 0 ? 0 : 2 - uint32_t(dot_pos); + const unsigned int right_offset = positive_part(k); + + const unsigned int left_shift_src = positive_part(dot_pos); + const unsigned int left_shift_dest = dot_pos > 0 ? left_shift_src + (k < 0) : left_offset; + const unsigned int left_shift_len = positive_part(static_cast(len) - static_cast(left_shift_src)); + + const unsigned int term_pos = len + right_offset + (left_shift_dest - left_shift_src); + + wrap::memmove(buffer + left_shift_dest, buffer + left_shift_src, left_shift_len); + wrap::memset(buffer, CharType('0'), left_offset); + wrap::memset(buffer + len, CharType('0'), right_offset); + buffer[actual_dot_pos] = '.'; + buffer[term_pos] = '\0'; + + return term_pos; + } + + /** \brief Makes final format corrections to have the string representation + * be properly and pretty printed (with decimal point in place, exponent + * part, where appropriate etc.). + * + * \tparam decimal_scientific_threshold the maximum number of digits in the + * string representation, when decimal format can be used (otherwise + * decimal exponent or *scientific* format is used). + * \tparam CharType character type (typically `char` or `wchar_t`) of the + * output buffer \p **buffer**. + * + * \param buffer character buffer with printed digits. + * \param len length of current representation in \p **buffer**. + * \param k decimal exponent of the value. + * + * \return number of characters written to the buffer. + * + * \see `print_decimal()` + * \see `print_scientific()` + */ + template + inline std::size_t prettify(CharType* buffer, const unsigned int len, const int k) noexcept + { + /* v = buffer * 10 ^ k + dot_pos is such that 10 ^ (dot_pos - 1) <= v < 10 ^ dot_pos + this way dot_pos gives the position of the comma. + */ + const int dot_pos = static_cast(len) + k; + + // is always positive, since dot_pos is negative only when k is negative + const std::size_t field_width = size_t((std::max)(dot_pos, -k)); + + switch (choose_format(field_width)) + { + case format::decimal: + return print_decimal(buffer, len, k); + + case format::scientific: + return print_scientific(buffer, len, dot_pos); + } + + // never reach here + return 0; + } +} + +#endif // FLOAXIE_PRETTIFY_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/print.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/print.h new file mode 100644 index 000000000..ba69668b9 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/print.h @@ -0,0 +1,63 @@ +/* + * Copyright 2015, 2016 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Several utility functions as printing binary representation of floating + * point values and other stuff. + * + */ + +#ifndef FLOAXIE_PRINT_H +#define FLOAXIE_PRINT_H + +#include +#include +#include + +#include +#include + +namespace floaxie +{ + /** \brief Prints `double` value in binary format, splitting sign, exponent + * and mantissa parts with spaces. + * + * Useful for debugging purposes. + */ + std::string inline print_binary(double f) + { + auto s(std::bitset<64>(type_punning_cast(f)).to_string()); + s.insert(1, 1, ' '); + s.insert(13, 1, ' '); + return s; + } + + /** \brief Print arbitrary numeric value in binary format. */ + template inline std::string print_binary(NumericType v) + { + return std::bitset()>(v).to_string(); + } + + /** \brief Print arbitrary numeric value as if it were `double`. */ + template inline std::string print_double_presentation(NumericType v) + { + auto s(std::bitset<64>(v).to_string()); + s.insert(1, 1, ' '); + s.insert(54, 1, ' '); + s.insert(56, 1, ' '); + return s; + } +} + +#endif // FLOAXIE_PRINT_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/static_pow.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/static_pow.h new file mode 100644 index 000000000..202478684 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/static_pow.h @@ -0,0 +1,164 @@ +/* + * Copyright 2015-2019 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef FLOAXIE_STATIC_POW_H +#define FLOAXIE_STATIC_POW_H + +#include +#include + +namespace floaxie +{ + /** \brief Helper class template to calculate integer positive power in + * compile time. + * + * Unspecialized template contains intermediate steps implementation. + * + * \tparam base base of the power. + * \tparam pow exponent to power base. + */ + template struct static_pow_helper + { + /** \brief Result of power calculation (in compile time). + * + * Is calculated recursively in intermediate steps. + */ + static const unsigned int value = base * static_pow_helper::value; + }; + + /** \brief Terminal specialization of `static_pow_helper` template. */ + template struct static_pow_helper + { + /** \brief Any value in zero power is `1`. */ + static const unsigned int value = 1; + }; + + /** \brief Handy wrapper function to calculate positive power in compile + * time. + * + * \tparam base base of the power. + * \tparam pow exponent to power base. + * + * \return Result of power calculation (in compile time). + */ + template constexpr unsigned long static_pow() + { + static_assert(base > 0, "Base should be positive"); + return static_pow_helper::value; + } + + /** \brief Helper structure template to append value to + * `std::integer_sequence`. + * + * \tparam T type of elements of `std::integer_sequence`. + * \tparam Add element to add to the specified sequence. + * \tparam Seq original `std::integer_sequence` expressed by parameter pack + * of its elements in template parameters. + */ + template struct concat_sequence; + + /** \brief Implements the concatenation itself. + * + * Steals parameters (read: contents) of passed `std::integer_sequence`-like + * type and creates another `std::integer_sequence`-like type with the + * specified value appended at the end. + */ + template struct concat_sequence> + { + /** \brief New `std::integer_sequence`-like type with the specified + * element added to the end. + */ + using type = std::integer_sequence; + }; + + /** \brief Helper structure template to convert `std::integer_sequence` + * to `std::array`. + * + * \tparam Seq sequence to convert. + */ + template struct make_integer_array; + + /** \brief Main specialization of `make_integer_array` with the specified + * input `std::integer_sequence`. + * + * \tparam T type of elements of `std::integer_sequence`. + * \tparam Ints elements of the `std::integer_sequence` to convert. + */ + template struct make_integer_array> + { + /** \brief Type of the resulting `std::array` (specialized with + * element type and length). */ + using type = std::array; + + /** \brief Instance of the resulting `std::array` filled in with + * elements from the specified `std::integer_sequence` in compile time. + */ + static constexpr type value = type{{Ints...}}; + }; + + /** \brief Creates `std::integer_sequence` with sequence of powers of + * \p **base** up to the exponent value, defined by \p **current_pow**. + * + * For example, if \p base is `10` and \p current_pow is `3`, the result + * will contain values `1000`, `100`, `10`, `1`. + * + * \tparam T type of elements. + * \tparam base base of powers to calculate. + * \tparam current_pow the maximum exponent value to calculate power for. + */ + template struct pow_sequencer + { + /** \brief Value of power on the current step (in recursion). */ + static const T value = pow_sequencer::value * base; + + /** \brief `std::integer_sequence`-like type containing all the + * calculated powers at the moment. + */ + typedef typename concat_sequence::sequence_type>::type sequence_type; + }; + + /** \brief Terminal step specialization for `pow_sequencer` template. */ + template struct pow_sequencer + { + /** \brief Zero power of base. */ + static constexpr T value = 1; + /** \brief `std::integer_sequence` with zero power only yet. */ + typedef std::integer_sequence sequence_type; + }; + + /** \brief Handy wrapper function to calculate a sequence of powers. + * + * \tparam T type of elements. + * \tparam base base for powers to calculate. + * \tparam max_pow maximum exponent, up to which the calculation will be + * performed. + * + * \return `std::array` filled with powers of the specified arguments in + * reverse order. I.e. for \p **T** of `unsigned int`, \p **base** of 10 + * and \p **max_pow** 3 this would be: + * `std::array({{1000, 100, 10, 1}})`. + */ + template constexpr T seq_pow(std::size_t pow) + { + typedef make_integer_array::sequence_type> maker; + constexpr typename maker::type arr(maker::value); + + return arr[pow]; + } +} + +#endif // FLOAXIE_STATIC_POW_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/type_punning_cast.h b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/type_punning_cast.h new file mode 100644 index 000000000..84f4ccb3f --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/floaxie/floaxie/type_punning_cast.h @@ -0,0 +1,45 @@ +/* + * Copyright 2015, 2016 Alexey Chernov <4ernov@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * type_punning_cast is copy-paste of pseudo_cast as posted by plasmacel + * at StackOverflow + * (http://stackoverflow.com/questions/17789928/whats-a-proper-way-of-type-punning-a-float-to-an-int-and-vice-versa) + * + */ + +#ifndef FLOAXIE_TYPE_PUNNING_CAST +#define FLOAXIE_TYPE_PUNNING_CAST + +#include + +namespace floaxie +{ + /** \brief Correct type-punning cast implementation to avoid any possible + * undefined behaviour. + * + * \see https://en.wikipedia.org/wiki/Type_punning + */ + template inline T type_punning_cast(const U& x) + { + static_assert(sizeof(T) == sizeof(U), + "type_punning_cast can't handle types with different size"); + + T to; + std::memcpy(&to, &x, sizeof(T)); + return to; + } +} + +#endif // FLOAXIE_TYPE_PUNNING_CAST diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/fpng.cpp b/contrib/tinyusdz/tinyusdz_repo/src/external/fpng.cpp new file mode 100644 index 000000000..e376779c8 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/fpng.cpp @@ -0,0 +1,3226 @@ +// fpng.cpp 1.0.6 - Fast 24/32bpp .PNG image writer/reader. See unlicense at the end of this file. +// PNG's generated by this code have been tested to load successfully with stb_image.h, lodepng.cpp, wuffs, libpng, and pngcheck. +// +// Uses code from the simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299 +// Some low-level Deflate/Huffman functions derived from the original 2011 Google Code version of miniz (public domain by R. Geldreich, Jr.): https://code.google.com/archive/p/miniz/ +// Low-level Huffman code size function: public domain, originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. +// +// Optional config macros: +// FPNG_NO_SSE - Set to 1 to completely disable SSE usage, even on x86/x64. By default, on x86/x64 it's enabled. +// FPNG_DISABLE_DECODE_CRC32_CHECKS - Set to 1 to disable PNG chunk CRC-32 tests, for improved fuzzing. Defaults to 0. +// FPNG_USE_UNALIGNED_LOADS - Set to 1 to indicate it's OK to read/write unaligned 32-bit/64-bit values. Defaults to 0, unless x86/x64. +// +// With gcc/clang on x86, compile with -msse4.1 -mpclmul -fno-strict-aliasing +// Only tested with -fno-strict-aliasing (which the Linux kernel uses, and MSVC's default). +// +#include "fpng.h" +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Weverything" +#endif + +#ifdef _MSC_VER + #pragma warning (disable:4127) // conditional expression is constant +#endif + +// Set FPNG_NO_SSE to 1 to completely disable SSE usage. +#ifndef FPNG_NO_SSE + #define FPNG_NO_SSE (1) +#endif + +// Detect if we're compiling on x86/x64 +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) + #define FPNG_X86_OR_X64_CPU (1) +#else + #define FPNG_X86_OR_X64_CPU (0) +#endif + +#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE + #ifdef _MSC_VER + #include + #endif + #include // SSE + #include // SSE2 + #include // SSE4.1 + #include // pclmul +#endif + +#ifndef FPNG_NO_STDIO + #include +#endif + +// Allow the disabling of the chunk data CRC32 checks, for fuzz testing of the decoder +#ifndef FPNG_DISABLE_DECODE_CRC32_CHECKS + #define FPNG_DISABLE_DECODE_CRC32_CHECKS (0) +#endif + +// Using unaligned loads and stores causes errors when using UBSan. Jam it off. +#if defined(__has_feature) + #if __has_feature(undefined_behavior_sanitizer) + #undef FPNG_USE_UNALIGNED_LOADS + #define FPNG_USE_UNALIGNED_LOADS (0) + #endif +#endif + +// Set to 0 if your platform doesn't support unaligned 32-bit/64-bit reads/writes. +#ifndef FPNG_USE_UNALIGNED_LOADS + #if FPNG_X86_OR_X64_CPU + // On x86/x64 we default to enabled, for a noticeable perf gain. + #define FPNG_USE_UNALIGNED_LOADS (1) + #else + #define FPNG_USE_UNALIGNED_LOADS (0) + #endif +#endif + +#if defined(_MSC_VER) || defined(__MINGW32__) || FPNG_X86_OR_X64_CPU + #ifndef __LITTLE_ENDIAN + #define __LITTLE_ENDIAN 1234 + #endif + #ifndef __BIG_ENDIAN + #define __BIG_ENDIAN 4321 + #endif + + // Assume little endian on Windows/x86/x64. + #define __BYTE_ORDER __LITTLE_ENDIAN +#elif defined(__APPLE__) + #define __BYTE_ORDER __BYTE_ORDER__ + #define __LITTLE_ENDIAN __LITTLE_ENDIAN__ + #define __BIG_ENDIAN __BIG_ENDIAN__ +#else + // for __BYTE_ORDER (__LITTLE_ENDIAN or __BIG_ENDIAN) + #include + + #ifndef __LITTLE_ENDIAN + #define __LITTLE_ENDIAN 1234 + #endif + #ifndef __BIG_ENDIAN + #define __BIG_ENDIAN 4321 + #endif +#endif + +#if !defined(__BYTE_ORDER) + #error __BYTE_ORDER undefined. Compile with -D__BYTE_ORDER=1234 for little endian or -D__BYTE_ORDER=4321 for big endian. +#endif + +namespace fpng +{ + static const int FPNG_FALSE = 0; + static const uint8_t FPNG_FDEC_VERSION = 0; + static const uint32_t FPNG_MAX_SUPPORTED_DIM = 1 << 24; + + template static inline S maximum(S a, S b) { return (a > b) ? a : b; } + template static inline S minimum(S a, S b) { return (a < b) ? a : b; } + + static inline uint32_t simple_swap32(uint32_t x) { return (x >> 24) | ((x >> 8) & 0x0000FF00) | ((x << 8) & 0x00FF0000) | (x << 24); } + static inline uint64_t simple_swap64(uint64_t x) { return (((uint64_t)simple_swap32((uint32_t)x)) << 32U) | simple_swap32((uint32_t)(x >> 32U)); } + + static inline uint32_t swap32(uint32_t x) + { +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap32(x); +#else + return simple_swap32(x); +#endif + } + + static inline uint64_t swap64(uint64_t x) + { +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap64(x); +#else + return simple_swap64(x); +#endif + } + +#if FPNG_USE_UNALIGNED_LOADS + #if __BYTE_ORDER == __BIG_ENDIAN + #define READ_LE32(p) swap32(*reinterpret_cast(p)) + #define WRITE_LE32(p, v) *reinterpret_cast(p) = swap32((uint32_t)(v)) + #define WRITE_LE64(p, v) *reinterpret_cast(p) = swap64((uint64_t)(v)) + + #define READ_BE32(p) *reinterpret_cast(p) + #else + #define READ_LE32(p) (*reinterpret_cast(p)) + #define WRITE_LE32(p, v) *reinterpret_cast(p) = (uint32_t)(v) + #define WRITE_LE64(p, v) *reinterpret_cast(p) = (uint64_t)(v) + + #define READ_BE32(p) swap32(*reinterpret_cast(p)) + #endif +#else + // A good compiler should be able to optimize these routines - hopefully. They are crucial for performance. + static inline uint32_t READ_LE32(const void* p) + { + const uint8_t* pBytes = (const uint8_t*)p; + return ((uint32_t)pBytes[0]) | (((uint32_t)pBytes[1]) << 8U) | (((uint32_t)pBytes[2]) << 16U) | (((uint32_t)pBytes[3]) << 24U); + } + + static inline uint32_t READ_BE32(const void* p) + { + const uint8_t* pBytes = (const uint8_t*)p; + return ((uint32_t)pBytes[3]) | (((uint32_t)pBytes[2]) << 8U) | (((uint32_t)pBytes[1]) << 16U) | (((uint32_t)pBytes[0]) << 24U); + } + + static inline void WRITE_LE32(const void* p, uint32_t v) + { + uint8_t* pBytes = (uint8_t*)p; + pBytes[0] = (uint8_t)(v); + pBytes[1] = (uint8_t)(v >> 8); + pBytes[2] = (uint8_t)(v >> 16); + pBytes[3] = (uint8_t)(v >> 24); + } + + static inline void WRITE_LE64(const void* p, uint64_t v) + { + uint8_t* pBytes = (uint8_t*)p; + pBytes[0] = (uint8_t)(v); + pBytes[1] = (uint8_t)(v >> 8); + pBytes[2] = (uint8_t)(v >> 16); + pBytes[3] = (uint8_t)(v >> 24); + pBytes[4] = (uint8_t)(v >> 32); + pBytes[5] = (uint8_t)(v >> 40); + pBytes[6] = (uint8_t)(v >> 48); + pBytes[7] = (uint8_t)(v >> 56); + } +#endif + + // Customized the very common case of reading a 24bpp pixel from memory + static inline uint32_t READ_RGB_PIXEL(const void* p) + { +#if FPNG_USE_UNALIGNED_LOADS + return READ_LE32(p) & 0xFFFFFF; +#else + const uint8_t* pBytes = (const uint8_t*)p; + return ((uint32_t)pBytes[0]) | (((uint32_t)pBytes[1]) << 8U) | (((uint32_t)pBytes[2]) << 16U); +#endif + } + + // See "Slicing by 4" CRC-32 algorithm here: + // https://create.stephan-brumme.com/crc32/ + + // Precomputed 4KB of CRC-32 tables + static const uint32_t g_crc32_4[4][256] = { + {00, 016701630226, 035603460454, 023102250672, 0733342031, 016032572217, 035130722465, 023631112643, 01666704062, 017167134244, 034065364436, 022764554610, 01155446053, 017654276275, 034756026407, 022057616621, 03555610144, 015254020362, 036356270510, 020457440736, 03266552175, 015567362353, 036465132521, 020364702707, 02333114126, 014432724300, 037530574572, 021231344754, 02400256117, 014301466331, 037203636543, 021502006765, + 07333420310, 011432210136, 032530040744, 024231670562, 07400762321, 011301152107, 032203302775, 024502532553, 06555324372, 010254514154, 033356744726, 025457174500, 06266066343, 010567656165, 033465406717, 025364236531, 04666230254, 012167400072, 031065650600, 027764060426, 04155172265, 012654742043, 031756512631, 027057322417, 05000534236, 013701304010, 030603154662, 026102764444, 05733676207, 013032046021, 030130216653, 026631426475, + 016667040620, 0166670406, 023064420274, 035765210052, 016154302611, 0655532437, 023757762245, 035056152063, 017001744642, 01700174464, 022602324216, 034103514030, 017732406673, 01033236455, 022131066227, 034630656001, 015332650764, 03433060542, 020531230330, 036230400116, 015401512755, 03300322573, 020202172301, 036503742127, 014554154706, 02255764520, 021357534352, 037456304174, 014267216737, 02566426511, 021464676363, 037365046145, + 011554460530, 07255250716, 024357000164, 032456630342, 011267722501, 07566112727, 024464342155, 032365572373, 010332364552, 06433554774, 025531704106, 033230134320, 010401026563, 06300616745, 025202446137, 033503276311, 012001270474, 04700440652, 027602610020, 031103020206, 012732132445, 04033702663, 027131552011, 031630362237, 013667574416, 05166344630, 026064114042, 030765724264, 013154636427, 05655006601, 026757256073, 030056466255, + 035556101440, 023257731666, 0355561014, 016454351232, 035265243471, 023564473657, 0466623025, 016367013203, 034330605422, 022431035604, 01533265076, 017232455250, 034403547413, 022302377635, 01200127047, 017501717261, 036003711504, 020702121722, 03600371150, 015101541376, 036730453535, 020031263713, 03133033161, 015632603347, 037665015566, 021164625740, 02066475132, 014767245314, 037156357557, 021657567771, 02755737103, 014054107325, + 032665521750, 024164311576, 07066141304, 011767771122, 032156663761, 024657053547, 07755203335, 011054433113, 033003225732, 025702415514, 06600645366, 010101075140, 033730167703, 025031757525, 06133507357, 010632337171, 031330331614, 027431501432, 04533751240, 012232161066, 031403073625, 027302643403, 04200413271, 012501223057, 030556435676, 026257205450, 05355055222, 013454665004, 030265777647, 026564147461, 05466317213, 013367527035, + 023331141260, 035430771046, 016532521634, 0233311412, 023402203251, 035303433077, 016201663605, 0500053423, 022557645202, 034256075024, 017354225656, 01455415470, 022264507233, 034565337015, 017467167667, 01366757441, 020664751324, 036165161102, 015067331770, 03766501556, 020157413315, 036656223133, 015754073741, 03055643567, 021002055346, 037703665160, 014601435712, 02100205534, 021731317377, 037030527151, 014132777723, 02633147505, + 024002561170, 032703351356, 011601101524, 07100731702, 024731623141, 032030013367, 011132243515, 07633473733, 025664265112, 033165455334, 010067605546, 06766035760, 025157127123, 033656717305, 010754547577, 06055377751, 027557371034, 031256541212, 012354711460, 04455121646, 027264033005, 031565603223, 012467453451, 04366263677, 026331475056, 030430245270, 013532015402, 05233625624, 026402737067, 030303107241, 013201357433, 05500567615, + }, { 00,03106630501,06215461202,05313251703,014433142404,017535772105,012626523606,011720313307,031066305010,032160535511,037273764212,034375154713,025455247414,026553477115,023640626616,020746016317,011260411121,012366221420,017075070323,014173640622,05653553525,06755363024,03446132727,0540702226,020206714131,023300124430,026013375333,025115545632,034635656535,037733066034,032420237737,031526407236, + 022541022242,021447612743,024754443040,027652273541,036172160646,035074750347,030367501444,033261331145,013527327252,010421517753,015732746050,016634176551,07114265656,04012455357,01301604454,02207034155,033721433363,030627203662,035534052161,036432662460,027312571767,024214341266,021107110565,022001720064,02747736373,01641106672,04552357171,07454567470,016374674777,015272044276,010161215575,013067425074, + 036036247405,035130477104,030223626607,033325016306,022405305001,021503535500,024610764203,027716154702,07050142415,04156772114,01245523617,02343313316,013463000011,010565630510,015676461213,016770251712,027256656524,024350066025,021043237726,022145407227,033665714120,030763124421,035470375322,036576545623,016230553534,015336363035,010025132736,013123702237,02603411130,01705221431,04416070332,07510640633, + 014577265647,017471455346,012762604445,011664034144,0144327243,03042517742,06351746041,05257176540,025511160657,026417750356,023704501455,020602331154,031122022253,032024612752,037337443051,034231273550,05717674766,06611044267,03502215564,0404425065,011324736362,012222106663,017131357160,014037567461,034771571776,037677341277,032564110574,031462720075,020342433372,023244203673,026157052170,025051662471, + 07340714113,04246124412,01155375311,02053545610,013773656517,010675066016,015566237715,016460407214,036326411103,035220221402,030133070301,033035640600,022715553507,021613363006,024500132705,027406702204,016120305032,015026535533,010335764230,013233154731,02513247436,01415477137,04706626634,07600016335,027146000022,024040630523,021353461220,022255251721,033575142426,030473772127,035760523624,036666313325, + 025601736351,026707106650,023414357153,020512567452,031232674755,032334044254,037027215557,034121425056,014667433341,017761203640,012472052143,011574662442,0254571745,03352341244,06041110547,05147720046,034461327270,037567517771,032674746072,031772176573,020052265674,023154455375,026247604476,025341034177,05407022260,06501612761,03612443062,0714273563,011034160664,012132750365,017221501466,014327331167, + 031376553516,032270363017,037163132714,034065702215,025745411112,026643221413,023550070310,020456640611,0310656506,03216066007,06105237704,05003407205,014723714102,017625124403,012536375300,011430545601,020116142437,023010772136,026303523635,025205313334,034525000033,037423630532,032730461231,031636251730,011170247427,012076477126,017365626625,014263016324,05543305023,06445535522,03756764221,0650154720, + 013637571754,010731341255,015422110556,016524720057,07204433350,04302203651,01011052152,02117662453,022651674744,021757044245,024444215546,027542425047,036262736340,035364106641,030077357142,033171567443,02457160675,01551750374,04642501477,07744331176,016064022271,015162612770,010271443073,013377273572,033431265665,030537455364,035624604467,036722034166,027002327261,024104517760,021217746063,022311176562, + }, { 00,0160465067,0341152156,0221537131,0702324334,0662741353,0443276262,0523613205,01604650670,01764235617,01545702726,01425367741,01106574544,01066111523,01247426412,01327043475,03411521560,03571144507,03750473436,03630016451,03313605654,03273260633,03052757702,03132332765,02215371310,02375714377,02154223246,02034646221,02517055024,02477430043,02656107172,02736562115, + 07023243340,07143626327,07362311216,07202774271,07721167074,07641502013,07460035122,07500450145,06627413530,06747076557,06566541466,06406124401,06125737604,06045352663,06264665752,06304200735,04432762620,04552307647,04773630776,04613255711,04330446514,04250023573,04071514442,04111171425,05236132050,05356557037,05177060106,05017405161,05534216364,05454673303,05675344232,05715721255, + 016046506700,016126163767,016307454656,016267031631,016744622434,016624247453,016405770562,016565315505,017642356170,017722733117,017503204026,017463661041,017140072244,017020417223,017201120312,017361545375,015457027260,015537442207,015716175336,015676510351,015355303154,015235766133,015014251002,015174634065,014253677410,014333212477,014112725546,014072340521,014551553724,014431136743,014610401672,014770064615, + 011065745440,011105320427,011324617516,011244272571,011767461774,011607004713,011426533622,011546156645,010661115230,010701570257,010520047366,010440422301,010163231104,010003654163,010222363052,010342706035,012474264120,012514601147,012735336076,012655753011,012376140214,012216525273,012037012342,012157477325,013270434750,013310051737,013131566606,013051103661,013572710464,013412375403,013633642532,013753227555, + 034115215600,034075670667,034254347756,034334722731,034617131534,034777554553,034556063462,034436406405,035711445070,035671020017,035450517126,035530172141,035013761344,035173304323,035352633212,035232256275,037504734360,037464351307,037645666236,037725203251,037206410054,037366075033,037147542102,037027127165,036300164510,036260501577,036041036446,036121453421,036402240624,036562625643,036743312772,036623777715, + 033136056540,033056433527,033277104416,033317561471,033634372674,033754717613,033575220722,033415645745,032732606330,032652263357,032473754266,032513331201,032030522004,032150147063,032371470152,032211015135,030527577020,030447112047,030666425176,030706040111,030225653314,030345236373,030164701242,030004364225,031323327650,031243742637,031062275706,031102610761,031421003564,031541466503,031760151432,031600534455, + 022153713100,022033376167,022212641056,022372224031,022651437234,022731052253,022510565362,022470100305,023757143770,023637526717,023416011626,023576474641,023055267444,023135602423,023314335512,023274750575,021542232460,021422657407,021603360536,021763705551,021240116754,021320573733,021101044602,021061421665,020346462210,020226007277,020007530346,020167155321,020444746124,020524323143,020705614072,020665271015, + 025170550240,025010135227,025231402316,025351067371,025672674174,025712211113,025533726022,025453343045,024774300430,024614765457,024435252566,024555637501,024076024704,024116441763,024337176652,024257513635,026561071720,026401414747,026620123676,026740546611,026263355414,026303730473,026122207542,026042662525,027365621150,027205244137,027024773006,027144316061,027467505264,027507160203,027726457332,027646032355, + }, { 00,027057063545,025202344213,02255327756,021730513527,06767570062,04532657734,023565634271,030555024357,017502047612,015757360144,032700303401,011265537670,036232554335,034067673463,013030610126,012006253637,035051230372,037204117424,010253174161,033736740310,014761723655,016534404103,031563467446,022553277560,05504214025,07751133773,020706150236,03263764047,024234707502,026061420254,01036443711, + 024014527476,03043544133,01216663665,026241600320,05724034151,022773057414,020526370342,07571313607,014541503721,033516560264,031743647532,016714624077,035271010206,012226073743,010073354015,037024337550,036012774241,011045717704,013210430052,034247453517,017722267766,030775204223,032520123575,015577140030,06547750116,021510733453,023745414305,04712477640,027277243431,0220220174,02075107622,025022164367, + 023305054075,04352037530,06107310266,021150373723,02435547552,025462524017,027637603741,0660660204,013650070322,034607013667,036452334131,011405357474,032160563605,015137500340,017362627416,030335644153,031303207642,016354264307,014101143451,033156120114,010433714365,037464777620,035631450176,012666433433,01656223515,026601240050,024454167706,03403104243,020166730032,07131753577,05364474221,022333417764, + 07311573403,020346510146,022113637610,05144654355,026421060124,01476003461,03623324337,024674347672,037644557754,010613534211,012446613547,035411670002,016174044273,031123027736,033376300060,014321363525,015317720234,032340743771,030115464027,017142407562,034427233713,013470250256,011625177500,036672114045,025642704163,02615767426,0440440370,027417423635,04172217444,023125274101,021370153657,06327130312, + 035526333073,012571350536,010724077260,037773014725,014216620554,033241643011,031014564747,016043507202,05073317324,022024374661,020271053137,07226030472,024743604603,03714667346,01541540410,026516523155,027520160644,0577103301,02722224457,025775247112,06210473363,021247410626,023012737170,04045754435,017075144513,030022127056,032277200700,015220263245,036745457034,011712434571,013547713227,034510770762, + 011532614405,036565677140,034730550616,013767533353,030202307122,017255364467,015000043331,032057020674,021067630752,06030653217,04265574541,023232517004,0757323275,027700340730,025555067066,02502004523,03534447232,024563424777,026736703021,01761760564,022204154715,05253137250,07006210506,020051273043,033061463165,014036400420,016263727376,031234744633,012751170442,035706113107,037553234651,010504257314, + 016623367006,031674304543,033421023215,014476040750,037113674521,010144617064,012311530732,035346553277,026376343351,01321320614,03174007142,024123064407,07446650676,020411633333,022644514465,05613577120,04625134631,023672157374,021427270422,06470213167,025115427316,02142444653,0317763105,027340700440,034370110566,013327173023,011172254775,036125237230,015440403041,032417460504,030642747252,017615724717, + 032637640470,015660623135,017435504663,030462567326,013107353157,034150330412,036305017344,011352074601,02362664727,025335607262,027160520534,0137543071,023452377200,04405314745,06650033013,021607050556,020631413247,07666470702,05433757054,022464734511,01101100760,026156163225,024303244573,03354227036,010364437110,037333454455,035166773303,012131710646,031454124437,016403147172,014656260624,033601203361, + } }; + + static uint32_t crc32_slice_by_4(const void* pData, size_t data_len, uint32_t cur_crc32 = 0) + { + uint32_t crc = ~cur_crc32; + const uint32_t* pData32 = static_cast(pData); + + for (; data_len >= sizeof(uint32_t); ++pData32, data_len -= 4) + { + uint32_t v = READ_LE32(pData32) ^ crc; + crc = g_crc32_4[0][v >> 24] ^ g_crc32_4[1][(v >> 16) & 0xFF] ^ g_crc32_4[2][(v >> 8) & 0xFF] ^ g_crc32_4[3][v & 0xFF]; + } + + for (const uint8_t* pData8 = reinterpret_cast(pData32); data_len; --data_len) + crc = (crc >> 8) ^ g_crc32_4[0][(crc & 0xFF) ^ *pData8++]; + + return ~crc; + } + +#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE + // See Fast CRC Computation for Generic Polynomials Using PCLMULQDQ Instruction": + // https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf + // Requires PCLMUL and SSE 4.1. This function skips Step 1 (fold by 4) for simplicity/less code. + static uint32_t crc32_pclmul(const uint8_t* p, size_t size, uint32_t crc) + { + assert(size >= 16); + + // See page 22 (bit reflected constants for gzip) +#ifdef _MSC_VER + static const uint64_t __declspec(align(16)) +#else + static const uint64_t __attribute__((aligned(16))) +#endif + s_u[2] = { 0x1DB710641, 0x1F7011641 }, s_k5k0[2] = { 0x163CD6124, 0 }, s_k3k4[2] = { 0x1751997D0, 0xCCAA009E }; + + // Load first 16 bytes, apply initial CRC32 + __m128i b = _mm_xor_si128(_mm_cvtsi32_si128(~crc), _mm_loadu_si128(reinterpret_cast(p))); + + // We're skipping directly to Step 2 page 12 - iteratively folding by 1 (by 4 is overkill for our needs) + const __m128i k3k4 = _mm_load_si128(reinterpret_cast(s_k3k4)); + + for (size -= 16, p += 16; size >= 16; size -= 16, p += 16) + b = _mm_xor_si128(_mm_xor_si128(_mm_clmulepi64_si128(b, k3k4, 17), _mm_loadu_si128(reinterpret_cast(p))), _mm_clmulepi64_si128(b, k3k4, 0)); + + // Final stages: fold to 64-bits, 32-bit Barrett reduction + const __m128i z = _mm_set_epi32(0, ~0, 0, ~0), u = _mm_load_si128(reinterpret_cast(s_u)); + b = _mm_xor_si128(_mm_srli_si128(b, 8), _mm_clmulepi64_si128(b, k3k4, 16)); + b = _mm_xor_si128(_mm_clmulepi64_si128(_mm_and_si128(b, z), _mm_loadl_epi64(reinterpret_cast(s_k5k0)), 0), _mm_srli_si128(b, 4)); + return ~_mm_extract_epi32(_mm_xor_si128(b, _mm_clmulepi64_si128(_mm_and_si128(_mm_clmulepi64_si128(_mm_and_si128(b, z), u, 16), z), u, 0)), 1); + } + + static uint32_t crc32_sse41_simd(const unsigned char* buf, size_t len, uint32_t prev_crc32) + { + if (len < 16) + return crc32_slice_by_4(buf, len, prev_crc32); + + uint32_t simd_len = len & ~15; + uint32_t c = crc32_pclmul(buf, simd_len, prev_crc32); + return crc32_slice_by_4(buf + simd_len, len - simd_len, c); + } +#endif + +#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE + +#ifndef _MSC_VER + static void do_cpuid(uint32_t eax, uint32_t ecx, uint32_t* regs) + { + uint32_t ebx = 0, edx = 0; + +#if defined(__PIC__) && defined(__i386__) + __asm__("movl %%ebx, %%edi;" + "cpuid;" + "xchgl %%ebx, %%edi;" + : "=D"(ebx), "+a"(eax), "+c"(ecx), "=d"(edx)); +#else + __asm__("cpuid;" : "+b"(ebx), "+a"(eax), "+c"(ecx), "=d"(edx)); +#endif + + regs[0] = eax; regs[1] = ebx; regs[2] = ecx; regs[3] = edx; + } +#endif + + struct cpu_info + { + cpu_info() { memset(this, 0, sizeof(*this)); } + + bool m_initialized, m_has_fpu, m_has_mmx, m_has_sse, m_has_sse2, m_has_sse3, m_has_ssse3, m_has_sse41, m_has_sse42, m_has_avx, m_has_avx2, m_has_pclmulqdq; + + void init() + { + if (m_initialized) + return; + + int regs[4]; + +#ifdef _MSC_VER + __cpuid(regs, 0); +#else + do_cpuid(0, 0, (uint32_t*)regs); +#endif + + const uint32_t max_eax = regs[0]; + if (max_eax >= 1U) + { +#ifdef _MSC_VER + __cpuid(regs, 1); +#else + do_cpuid(1, 0, (uint32_t*)regs); +#endif + extract_x86_flags(regs[2], regs[3]); + } + + if (max_eax >= 7U) + { +#ifdef _MSC_VER + __cpuidex(regs, 7, 0); +#else + do_cpuid(7, 0, (uint32_t*)regs); +#endif + extract_x86_extended_flags(regs[1]); + } + + m_initialized = true; + } + + bool can_use_sse41() const { return m_has_sse && m_has_sse2 && m_has_sse3 && m_has_ssse3 && m_has_sse41; } + bool can_use_pclmul() const { return m_has_pclmulqdq && can_use_sse41(); } + + private: + void extract_x86_flags(uint32_t ecx, uint32_t edx) + { + m_has_fpu = (edx & (1 << 0)) != 0; m_has_mmx = (edx & (1 << 23)) != 0; m_has_sse = (edx & (1 << 25)) != 0; m_has_sse2 = (edx & (1 << 26)) != 0; + m_has_sse3 = (ecx & (1 << 0)) != 0; m_has_ssse3 = (ecx & (1 << 9)) != 0; m_has_sse41 = (ecx & (1 << 19)) != 0; m_has_sse42 = (ecx & (1 << 20)) != 0; + m_has_pclmulqdq = (ecx & (1 << 1)) != 0; m_has_avx = (ecx & (1 << 28)) != 0; + } + + void extract_x86_extended_flags(uint32_t ebx) { m_has_avx2 = (ebx & (1 << 5)) != 0; } + }; + + cpu_info g_cpu_info; + + void fpng_init() + { + g_cpu_info.init(); + } +#else + void fpng_init() + { + } +#endif + + bool fpng_cpu_supports_sse41() + { +#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE + assert(g_cpu_info.m_initialized); + return g_cpu_info.can_use_sse41(); +#else + return false; +#endif + } + + uint32_t fpng_crc32(const void* pData, size_t size, uint32_t prev_crc32) + { +#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE + if (g_cpu_info.can_use_pclmul()) + return crc32_sse41_simd(static_cast(pData), size, prev_crc32); +#endif + + return crc32_slice_by_4(pData, size, prev_crc32); + } + +#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE + // See "Fast Computation of Adler32 Checksums": + // https://www.intel.com/content/www/us/en/developer/articles/technical/fast-computation-of-adler32-checksums.html + // SSE 4.1, 16 bytes per iteration + static uint32_t adler32_sse_16(const uint8_t* p, size_t len, uint32_t initial) + { + uint32_t s1 = initial & 0xFFFF, s2 = initial >> 16; + const uint32_t K = 65521; + + while (len >= 16) + { + __m128i a = _mm_setr_epi32(s1, 0, 0, 0), b = _mm_setzero_si128(), c = _mm_setzero_si128(), d = _mm_setzero_si128(), + e = _mm_setzero_si128(), f = _mm_setzero_si128(), g = _mm_setzero_si128(), h = _mm_setzero_si128(); + + const size_t n = minimum(len >> 4, 5552); + + for (size_t i = 0; i < n; i++) + { + const __m128i v = _mm_loadu_si128((const __m128i*)(p + i * 16)); + a = _mm_add_epi32(a, _mm_cvtepu8_epi32(_mm_shuffle_epi32(v, _MM_SHUFFLE(0, 0, 0, 0)))); b = _mm_add_epi32(b, a); + c = _mm_add_epi32(c, _mm_cvtepu8_epi32(_mm_shuffle_epi32(v, _MM_SHUFFLE(1, 1, 1, 1)))); d = _mm_add_epi32(d, c); + e = _mm_add_epi32(e, _mm_cvtepu8_epi32(_mm_shuffle_epi32(v, _MM_SHUFFLE(2, 2, 2, 2)))); f = _mm_add_epi32(f, e); + g = _mm_add_epi32(g, _mm_cvtepu8_epi32(_mm_shuffle_epi32(v, _MM_SHUFFLE(3, 3, 3, 3)))); h = _mm_add_epi32(h, g); + } + + uint32_t sa[16], sb[16]; + _mm_storeu_si128((__m128i*)sa, a); _mm_storeu_si128((__m128i*)(sa + 4), c); + _mm_storeu_si128((__m128i*)sb, b); _mm_storeu_si128((__m128i*)(sb + 4), d); + _mm_storeu_si128((__m128i*)(sa + 8), e); _mm_storeu_si128((__m128i*)(sa + 12), g); + _mm_storeu_si128((__m128i*)(sb + 8), f); _mm_storeu_si128((__m128i*)(sb + 12), h); + + // This could be vectorized, but it's only executed every 5552*16 iterations. + uint64_t vs1 = 0; + for (uint32_t i = 0; i < 16; i++) + vs1 += sa[i]; + + uint64_t vs2_a = 0; + for (uint32_t i = 0; i < 16; i++) + vs2_a += sa[i] * (uint64_t)i; + uint64_t vs2_b = 0; + for (uint32_t i = 0; i < 16; i++) + vs2_b += sb[i]; + vs2_b *= 16U; + uint64_t vs2 = vs2_b - vs2_a + s2; + + s1 = (uint32_t)(vs1 % K); + s2 = (uint32_t)(vs2 % K); + + p += n * 16; + len -= n * 16; + } + + for (; len; len--) + { + s1 += *p++; + s2 += s1; + } + + return (s1 % K) | ((s2 % K) << 16); + } +#endif + + static uint32_t fpng_adler32_scalar(const uint8_t* ptr, size_t buf_len, uint32_t adler) + { + uint32_t i, s1 = (uint32_t)(adler & 0xffff), s2 = (uint32_t)(adler >> 16); uint32_t block_len = (uint32_t)(buf_len % 5552); + if (!ptr) return FPNG_ADLER32_INIT; + while (buf_len) { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { + s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; + } + return (s2 << 16) + s1; + } + + uint32_t fpng_adler32(const void* pData, size_t size, uint32_t adler) + { +#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE + if (g_cpu_info.can_use_sse41()) + return adler32_sse_16((const uint8_t*)pData, size, adler); +#endif + return fpng_adler32_scalar((const uint8_t*)pData, size, adler); + } + + // Ensure we've been configured for endianness correctly. + static inline bool endian_check() + { + uint32_t endian_check = 0; + WRITE_LE32(&endian_check, 0x1234ABCD); + const uint32_t first_byte = reinterpret_cast(&endian_check)[0]; + return first_byte == 0xCD; + } + + static const uint16_t g_defl_len_sym[256] = { + 257,258,259,260,261,262,263,264,265,265,266,266,267,267,268,268,269,269,269,269,270,270,270,270,271,271,271,271,272,272,272,272, + 273,273,273,273,273,273,273,273,274,274,274,274,274,274,274,274,275,275,275,275,275,275,275,275,276,276,276,276,276,276,276,276, + 277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278, + 279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280, + 281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281, + 282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282, + 283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283, + 284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,285 }; + + static const uint8_t g_defl_len_extra[256] = { + 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,0 }; + + static const uint8_t g_defl_small_dist_sym[512] = { + 0,1,2,3,4,4,5,5,6,6,6,6,7,7,7,7,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14,14,14, + 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14, + 14,14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, + 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17 }; + + static const uint32_t g_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + + // Huffman tables generated by fpng_test -t @filelist.txt. Total alpha files : 1440, Total opaque files : 5627. + // Feel free to retrain the encoder on your opaque/alpha PNG files by setting FPNG_TRAIN_HUFFMAN_TABLES and running fpng_test with the -t option. + static const uint8_t g_dyn_huff_3[] = { + 120, 1, 237, 195, 3, 176, 110, 89, 122, 128, 225, 247, 251, 214, 218, 248, 113, 124, 173, 190, 109, 12, 50, 201, 196, 182, 109, 219, 182, 109, 219, 182, + 109, 219, 201, 36, 147, 153, 105, 235, 246, 53, 142, 207, 143, 141, 181, 214, 151, 93, 117, 170, 78, 117, 117, 58, 206, 77, 210, 217, 169, 122 }; + const uint32_t DYN_HUFF_3_BITBUF = 30, DYN_HUFF_3_BITBUF_SIZE = 7; + static const struct { uint8_t m_code_size; uint16_t m_code; } g_dyn_huff_3_codes[288] = { + {2,0},{4,2},{4,10},{5,14},{5,30},{6,25},{6,57},{6,5},{6,37},{7,3},{7,67},{7,35},{7,99},{8,11},{8,139},{8,75},{8,203},{8,43},{8,171},{8,107},{9,135},{9,391},{9,71},{9,327},{9,199},{9,455},{9,39},{9,295},{9,167},{9,423},{9,103},{10,183}, + {9,359},{10,695},{10,439},{10,951},{10,119},{10,631},{10,375},{10,887},{10,247},{10,759},{10,503},{11,975},{11,1999},{11,47},{11,1071},{12,1199},{11,559},{12,3247},{12,687},{11,1583},{12,2735},{12,1711},{12,3759},{12,431},{12,2479},{12,1455},{12,3503},{12,943},{12,2991},{12,1967},{12,4015},{12,111}, + {12,2159},{12,1135},{12,3183},{12,623},{12,2671},{12,1647},{12,3695},{12,367},{12,2415},{12,1391},{12,3439},{12,879},{12,2927},{12,1903},{12,3951},{12,239},{12,2287},{12,1263},{12,3311},{12,751},{12,2799},{12,1775},{12,3823},{12,495},{12,2543},{12,1519},{12,3567},{12,1007},{12,3055},{12,2031},{12,4079},{12,31}, + {12,2079},{12,1055},{12,3103},{12,543},{12,2591},{12,1567},{12,3615},{12,287},{12,2335},{12,1311},{12,3359},{12,799},{12,2847},{12,1823},{12,3871},{12,159},{12,2207},{12,1183},{12,3231},{12,671},{12,2719},{12,1695},{12,3743},{12,415},{12,2463},{12,1439},{12,3487},{12,927},{12,2975},{12,1951},{12,3999},{12,95}, + {12,2143},{12,1119},{12,3167},{12,607},{12,2655},{12,1631},{12,3679},{12,351},{12,2399},{12,1375},{12,3423},{12,863},{12,2911},{12,1887},{12,3935},{12,223},{12,2271},{12,1247},{12,3295},{12,735},{12,2783},{12,1759},{12,3807},{12,479},{12,2527},{12,1503},{12,3551},{12,991},{12,3039},{12,2015},{12,4063},{12,63}, + {12,2111},{12,1087},{12,3135},{12,575},{12,2623},{12,1599},{12,3647},{12,319},{12,2367},{12,1343},{12,3391},{12,831},{12,2879},{12,1855},{12,3903},{12,191},{12,2239},{12,1215},{12,3263},{12,703},{12,2751},{12,1727},{12,3775},{12,447},{12,2495},{12,1471},{12,3519},{12,959},{12,3007},{12,1983},{12,4031},{12,127}, + {12,2175},{12,1151},{12,3199},{12,639},{12,2687},{12,1663},{12,3711},{12,383},{12,2431},{12,1407},{12,3455},{12,895},{12,2943},{11,303},{12,1919},{12,3967},{11,1327},{12,255},{11,815},{11,1839},{11,175},{10,1015},{10,15},{10,527},{10,271},{10,783},{10,143},{10,655},{10,399},{10,911},{10,79},{10,591}, + {9,231},{10,335},{9,487},{9,23},{9,279},{9,151},{9,407},{9,87},{9,343},{9,215},{9,471},{9,55},{8,235},{8,27},{8,155},{8,91},{8,219},{8,59},{8,187},{8,123},{7,19},{7,83},{7,51},{7,115},{6,21},{6,53},{6,13},{6,45},{5,1},{5,17},{5,9},{4,6}, + {12,2303},{6,29},{0,0},{0,0},{8,251},{0,0},{0,0},{8,7},{0,0},{10,847},{0,0},{10,207},{12,1279},{10,719},{12,3327},{12,767},{12,2815},{12,1791},{12,3839},{12,511},{12,2559},{12,1535},{9,311},{12,3583},{12,1023},{12,3071},{10,463},{12,2047},{6,61},{12,4095},{0,0},{0,0} + }; + + static const uint8_t g_dyn_huff_4[] = { + 120, 1, 229, 196, 99, 180, 37, 103, 218, 128, 225, 251, 121, 171, 106, 243, 216, 231, 180, 109, 196, 182, 51, 51, 73, 6, 201, 216, 182, 109, 219, 182, + 17, 140, 98, 219, 102, 219, 60, 125, 172, 205, 170, 122, 159, 111, 213, 143, 179, 214, 94, 189, 58, 153, 104, 166, 103, 190, 247, 199, 117 }; + const uint32_t DYN_HUFF_4_BITBUF = 1, DYN_HUFF_4_BITBUF_SIZE = 2; + static const struct { uint8_t m_code_size; uint16_t m_code; } g_dyn_huff_4_codes[288] = { + {2,0},{4,2},{5,6},{6,30},{6,62},{6,1},{7,41},{7,105},{7,25},{7,89},{7,57},{7,121},{8,117},{8,245},{8,13},{8,141},{8,77},{8,205},{8,45},{8,173},{8,109},{8,237},{8,29},{8,157},{8,93},{8,221},{8,61},{9,83},{9,339},{9,211},{9,467},{9,51}, + {9,307},{9,179},{9,435},{9,115},{9,371},{9,243},{9,499},{9,11},{9,267},{9,139},{9,395},{9,75},{9,331},{9,203},{9,459},{9,43},{9,299},{10,7},{10,519},{10,263},{10,775},{10,135},{10,647},{10,391},{10,903},{10,71},{10,583},{10,327},{10,839},{10,199},{10,711},{10,455}, + {10,967},{10,39},{10,551},{10,295},{10,807},{10,167},{10,679},{10,423},{10,935},{10,103},{10,615},{11,463},{11,1487},{11,975},{10,359},{10,871},{10,231},{11,1999},{11,47},{11,1071},{11,559},{10,743},{10,487},{11,1583},{11,303},{11,1327},{11,815},{11,1839},{11,175},{11,1199},{11,687},{11,1711}, + {11,431},{11,1455},{11,943},{11,1967},{11,111},{11,1135},{11,623},{11,1647},{11,367},{11,1391},{11,879},{11,1903},{11,239},{11,1263},{11,751},{11,1775},{11,495},{11,1519},{11,1007},{11,2031},{11,31},{11,1055},{11,543},{11,1567},{11,287},{11,1311},{11,799},{11,1823},{11,159},{11,1183},{11,671},{11,1695}, + {11,415},{11,1439},{11,927},{11,1951},{11,95},{11,1119},{11,607},{11,1631},{11,351},{11,1375},{11,863},{11,1887},{11,223},{11,1247},{11,735},{11,1759},{11,479},{11,1503},{11,991},{11,2015},{11,63},{11,1087},{11,575},{11,1599},{11,319},{11,1343},{11,831},{11,1855},{11,191},{11,1215},{11,703},{11,1727}, + {11,447},{11,1471},{11,959},{11,1983},{11,127},{11,1151},{11,639},{11,1663},{11,383},{10,999},{10,23},{10,535},{10,279},{11,1407},{11,895},{11,1919},{11,255},{11,1279},{10,791},{10,151},{10,663},{10,407},{10,919},{10,87},{10,599},{10,343},{10,855},{10,215},{10,727},{10,471},{10,983},{10,55}, + {10,567},{10,311},{10,823},{10,183},{10,695},{10,439},{10,951},{10,119},{10,631},{10,375},{10,887},{10,247},{10,759},{10,503},{10,1015},{10,15},{10,527},{10,271},{10,783},{10,143},{10,655},{10,399},{9,171},{9,427},{9,107},{9,363},{9,235},{9,491},{9,27},{9,283},{9,155},{9,411}, + {9,91},{9,347},{9,219},{9,475},{9,59},{9,315},{9,187},{9,443},{8,189},{9,123},{8,125},{8,253},{8,3},{8,131},{8,67},{8,195},{8,35},{8,163},{8,99},{8,227},{8,19},{7,5},{7,69},{7,37},{7,101},{7,21},{7,85},{6,33},{6,17},{6,49},{5,22},{4,10}, + {12,2047},{0,0},{6,9},{0,0},{0,0},{0,0},{8,147},{0,0},{0,0},{7,53},{0,0},{9,379},{0,0},{9,251},{10,911},{10,79},{11,767},{10,591},{10,335},{10,847},{10,207},{10,719},{11,1791},{11,511},{9,507},{11,1535},{11,1023},{12,4095},{5,14},{0,0},{0,0},{0,0} + }; + +#define PUT_BITS(bb, ll) do { uint32_t b = bb, l = ll; assert((l) >= 0 && (l) <= 16); assert((b) < (1ULL << (l))); bit_buf |= (((uint64_t)(b)) << bit_buf_size); bit_buf_size += (l); assert(bit_buf_size <= 64); } while(0) +#define PUT_BITS_CZ(bb, ll) do { uint32_t b = bb, l = ll; assert((l) >= 1 && (l) <= 16); assert((b) < (1ULL << (l))); bit_buf |= (((uint64_t)(b)) << bit_buf_size); bit_buf_size += (l); assert(bit_buf_size <= 64); } while(0) + +#define PUT_BITS_FLUSH do { \ + if ((dst_ofs + 8) > dst_buf_size) \ + return 0; \ + WRITE_LE64(pDst + dst_ofs, bit_buf); \ + uint32_t bits_to_shift = bit_buf_size & ~7; \ + dst_ofs += (bits_to_shift >> 3); \ + assert(bits_to_shift < 64); \ + bit_buf = bit_buf >> bits_to_shift; \ + bit_buf_size -= bits_to_shift; \ +} while(0) + +#define PUT_BITS_FORCE_FLUSH do { \ + while (bit_buf_size > 0) \ + { \ + if ((dst_ofs + 1) > dst_buf_size) \ + return 0; \ + *(uint8_t*)(pDst + dst_ofs) = (uint8_t)bit_buf; \ + dst_ofs++; \ + bit_buf >>= 8; \ + bit_buf_size -= 8; \ + } \ +} while(0) + + enum + { + DEFL_MAX_HUFF_TABLES = 3, + DEFL_MAX_HUFF_SYMBOLS = 288, + DEFL_MAX_HUFF_SYMBOLS_0 = 288, + DEFL_MAX_HUFF_SYMBOLS_1 = 32, + DEFL_MAX_HUFF_SYMBOLS_2 = 19, + DEFL_LZ_DICT_SIZE = 32768, + DEFL_LZ_DICT_SIZE_MASK = DEFL_LZ_DICT_SIZE - 1, + DEFL_MIN_MATCH_LEN = 3, + DEFL_MAX_MATCH_LEN = 258 + }; + +#if FPNG_TRAIN_HUFFMAN_TABLES + uint64_t g_huff_counts[HUFF_COUNTS_SIZE]; +#endif + + struct defl_huff + { + uint16_t m_huff_count[DEFL_MAX_HUFF_TABLES][DEFL_MAX_HUFF_SYMBOLS]; + uint16_t m_huff_codes[DEFL_MAX_HUFF_TABLES][DEFL_MAX_HUFF_SYMBOLS]; + uint8_t m_huff_code_sizes[DEFL_MAX_HUFF_TABLES][DEFL_MAX_HUFF_SYMBOLS]; + }; + + struct defl_sym_freq + { + uint16_t m_key; + uint16_t m_sym_index; + }; + +#define DEFL_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) + + static defl_sym_freq* defl_radix_sort_syms(uint32_t num_syms, defl_sym_freq* pSyms0, defl_sym_freq* pSyms1) + { + uint32_t total_passes = 2, pass_shift, pass, i, hist[256 * 2]; defl_sym_freq* pCur_syms = pSyms0, * pNew_syms = pSyms1; DEFL_CLEAR_OBJ(hist); + for (i = 0; i < num_syms; i++) { uint32_t freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; } + while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--; + for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) + { + const uint32_t* pHist = &hist[pass << 8]; + uint32_t offsets[256], cur_ofs = 0; + for (i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; } + for (i = 0; i < num_syms; i++) pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; + { defl_sym_freq* t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; } + } + return pCur_syms; + } + + // defl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. + static void defl_calculate_minimum_redundancy(defl_sym_freq* A, int n) + { + int root, leaf, next, avbl, used, dpth; + if (n == 0) return; else if (n == 1) { A[0].m_key = 1; return; } + A[0].m_key += A[1].m_key; root = 0; leaf = 2; + for (next = 1; next < n - 1; next++) + { + if (leaf >= n || A[root].m_key < A[leaf].m_key) { A[next].m_key = A[root].m_key; A[root++].m_key = (uint16_t)next; } + else A[next].m_key = A[leaf++].m_key; + if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) { A[next].m_key = (uint16_t)(A[next].m_key + A[root].m_key); A[root++].m_key = (uint16_t)next; } + else A[next].m_key = (uint16_t)(A[next].m_key + A[leaf++].m_key); + } + A[n - 2].m_key = 0; for (next = n - 3; next >= 0; next--) A[next].m_key = A[A[next].m_key].m_key + 1; + avbl = 1; used = dpth = 0; root = n - 2; next = n - 1; + while (avbl > 0) + { + while (root >= 0 && (int)A[root].m_key == dpth) { used++; root--; } + while (avbl > used) { A[next--].m_key = (uint16_t)(dpth); avbl--; } + avbl = 2 * used; dpth++; used = 0; + } + } + + // Limits canonical Huffman code table's max code size. + enum { DEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 }; + static void defl_huffman_enforce_max_code_size(int* pNum_codes, int code_list_len, int max_code_size) + { + int i; uint32_t total = 0; if (code_list_len <= 1) return; + for (i = max_code_size + 1; i <= DEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i]; + for (i = max_code_size; i > 0; i--) total += (((uint32_t)pNum_codes[i]) << (max_code_size - i)); + while (total != (1UL << max_code_size)) + { + pNum_codes[max_code_size]--; + for (i = max_code_size - 1; i > 0; i--) if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; } + total--; + } + } + + static void defl_optimize_huffman_table(defl_huff* d, int table_num, int table_len, int code_size_limit, int static_table) + { + int i, j, l, num_codes[1 + DEFL_MAX_SUPPORTED_HUFF_CODESIZE]; uint32_t next_code[DEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; DEFL_CLEAR_OBJ(num_codes); + if (static_table) + { + for (i = 0; i < table_len; i++) num_codes[d->m_huff_code_sizes[table_num][i]]++; + } + else + { + defl_sym_freq syms0[DEFL_MAX_HUFF_SYMBOLS], syms1[DEFL_MAX_HUFF_SYMBOLS], * pSyms; + int num_used_syms = 0; + const uint16_t* pSym_count = &d->m_huff_count[table_num][0]; + for (i = 0; i < table_len; i++) if (pSym_count[i]) { syms0[num_used_syms].m_key = (uint16_t)pSym_count[i]; syms0[num_used_syms++].m_sym_index = (uint16_t)i; } + + pSyms = defl_radix_sort_syms(num_used_syms, syms0, syms1); defl_calculate_minimum_redundancy(pSyms, num_used_syms); + + for (i = 0; i < num_used_syms; i++) num_codes[pSyms[i].m_key]++; + + defl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); + + DEFL_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); DEFL_CLEAR_OBJ(d->m_huff_codes[table_num]); + for (i = 1, j = num_used_syms; i <= code_size_limit; i++) + for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (uint8_t)(i); + } + + next_code[1] = 0; for (j = 0, i = 2; i <= code_size_limit; i++) next_code[i] = j = ((j + num_codes[i - 1]) << 1); + + for (i = 0; i < table_len; i++) + { + uint32_t rev_code = 0, code, code_size; if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) continue; + code = next_code[code_size]++; for (l = code_size; l > 0; l--, code >>= 1) rev_code = (rev_code << 1) | (code & 1); + d->m_huff_codes[table_num][i] = (uint16_t)rev_code; + } + } + +#define DEFL_RLE_PREV_CODE_SIZE() { if (rle_repeat_count) { \ + if (rle_repeat_count < 3) { \ + d->m_huff_count[2][prev_code_size] = (uint16_t)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ + while (rle_repeat_count--) packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ + } else { \ + d->m_huff_count[2][16] = (uint16_t)(d->m_huff_count[2][16] + 1); packed_code_sizes[num_packed_code_sizes++] = 16; packed_code_sizes[num_packed_code_sizes++] = (uint8_t)(rle_repeat_count - 3); \ +} rle_repeat_count = 0; } } + +#define DEFL_RLE_ZERO_CODE_SIZE() { if (rle_z_count) { \ + if (rle_z_count < 3) { \ + d->m_huff_count[2][0] = (uint16_t)(d->m_huff_count[2][0] + rle_z_count); while (rle_z_count--) packed_code_sizes[num_packed_code_sizes++] = 0; \ + } else if (rle_z_count <= 10) { \ + d->m_huff_count[2][17] = (uint16_t)(d->m_huff_count[2][17] + 1); packed_code_sizes[num_packed_code_sizes++] = 17; packed_code_sizes[num_packed_code_sizes++] = (uint8_t)(rle_z_count - 3); \ + } else { \ + d->m_huff_count[2][18] = (uint16_t)(d->m_huff_count[2][18] + 1); packed_code_sizes[num_packed_code_sizes++] = 18; packed_code_sizes[num_packed_code_sizes++] = (uint8_t)(rle_z_count - 11); \ +} rle_z_count = 0; } } + + static uint8_t g_defl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + +#define DEFL_DYN_PUT_BITS(bb, ll) \ +do { \ + uint32_t b = (bb), l = (ll); \ + assert((l) >= 1 && (l) <= 16); assert((b) < (1ULL << (l))); \ + bit_buf |= (((uint64_t)(b)) << bit_buf_size); bit_buf_size += (l); assert(bit_buf_size <= 64); \ + while (bit_buf_size >= 8) \ + { \ + if ((dst_ofs + 1) > dst_buf_size) \ + return false; \ + *(uint8_t*)(pDst + dst_ofs) = (uint8_t)bit_buf; \ + dst_ofs++; \ + bit_buf >>= 8; \ + bit_buf_size -= 8; \ + } \ +} while(0) + + static bool defl_start_dynamic_block(defl_huff* d, uint8_t* pDst, uint32_t& dst_ofs, uint32_t dst_buf_size, uint64_t& bit_buf, int& bit_buf_size) + { + int num_lit_codes, num_dist_codes, num_bit_lengths; uint32_t i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; + uint8_t code_sizes_to_pack[DEFL_MAX_HUFF_SYMBOLS_0 + DEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[DEFL_MAX_HUFF_SYMBOLS_0 + DEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF; + +#if FPNG_TRAIN_HUFFMAN_TABLES + assert(HUFF_COUNTS_SIZE == DEFL_MAX_HUFF_SYMBOLS_0); + for (uint32_t i = 0; i < DEFL_MAX_HUFF_SYMBOLS_0; i++) + g_huff_counts[i] += d->m_huff_count[0][i]; +#endif + + d->m_huff_count[0][256] = 1; + + defl_optimize_huffman_table(d, 0, DEFL_MAX_HUFF_SYMBOLS_0, 12, FPNG_FALSE); + defl_optimize_huffman_table(d, 1, DEFL_MAX_HUFF_SYMBOLS_1, 12, FPNG_FALSE); + + for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) if (d->m_huff_code_sizes[0][num_lit_codes - 1]) break; + for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) if (d->m_huff_code_sizes[1][num_dist_codes - 1]) break; + + memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); + memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); + total_code_sizes_to_pack = num_lit_codes + num_dist_codes; num_packed_code_sizes = 0; rle_z_count = 0; rle_repeat_count = 0; + + memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * DEFL_MAX_HUFF_SYMBOLS_2); + for (i = 0; i < total_code_sizes_to_pack; i++) + { + uint8_t code_size = code_sizes_to_pack[i]; + if (!code_size) + { + DEFL_RLE_PREV_CODE_SIZE(); + if (++rle_z_count == 138) { DEFL_RLE_ZERO_CODE_SIZE(); } + } + else + { + DEFL_RLE_ZERO_CODE_SIZE(); + if (code_size != prev_code_size) + { + DEFL_RLE_PREV_CODE_SIZE(); + d->m_huff_count[2][code_size] = (uint16_t)(d->m_huff_count[2][code_size] + 1); packed_code_sizes[num_packed_code_sizes++] = code_size; + } + else if (++rle_repeat_count == 6) + { + DEFL_RLE_PREV_CODE_SIZE(); + } + } + prev_code_size = code_size; + } + if (rle_repeat_count) { DEFL_RLE_PREV_CODE_SIZE(); } + else { DEFL_RLE_ZERO_CODE_SIZE(); } + + defl_optimize_huffman_table(d, 2, DEFL_MAX_HUFF_SYMBOLS_2, 7, FPNG_FALSE); + + // max of 2+5+5+4+18*3+(288+32)*7=2310 bits + DEFL_DYN_PUT_BITS(2, 2); + + DEFL_DYN_PUT_BITS(num_lit_codes - 257, 5); + DEFL_DYN_PUT_BITS(num_dist_codes - 1, 5); + + for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) if (d->m_huff_code_sizes[2][g_defl_packed_code_size_syms_swizzle[num_bit_lengths]]) break; + num_bit_lengths = maximum(4, (num_bit_lengths + 1)); DEFL_DYN_PUT_BITS(num_bit_lengths - 4, 4); + for (i = 0; (int)i < num_bit_lengths; i++) DEFL_DYN_PUT_BITS(d->m_huff_code_sizes[2][g_defl_packed_code_size_syms_swizzle[i]], 3); + + for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes; ) + { + uint32_t code = packed_code_sizes[packed_code_sizes_index++]; assert(code < DEFL_MAX_HUFF_SYMBOLS_2); + DEFL_DYN_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); + if (code >= 16) DEFL_DYN_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); + } + + return true; + } + + static uint32_t write_raw_block(const uint8_t* pSrc, uint32_t src_len, uint8_t* pDst, uint32_t dst_buf_size) + { + if (dst_buf_size < 2) + return 0; + + pDst[0] = 0x78; + pDst[1] = 0x01; + + uint32_t dst_ofs = 2; + + uint32_t src_ofs = 0; + while (src_ofs < src_len) + { + const uint32_t src_remaining = src_len - src_ofs; + const uint32_t block_size = minimum(UINT16_MAX, src_remaining); + const bool final_block = (block_size == src_remaining); + + if ((dst_ofs + 5 + block_size) > dst_buf_size) + return 0; + + pDst[dst_ofs + 0] = final_block ? 1 : 0; + + pDst[dst_ofs + 1] = block_size & 0xFF; + pDst[dst_ofs + 2] = (block_size >> 8) & 0xFF; + + pDst[dst_ofs + 3] = (~block_size) & 0xFF; + pDst[dst_ofs + 4] = ((~block_size) >> 8) & 0xFF; + + memcpy(pDst + dst_ofs + 5, pSrc + src_ofs, block_size); + + src_ofs += block_size; + dst_ofs += 5 + block_size; + } + + uint32_t src_adler32 = fpng_adler32(pSrc, src_len, FPNG_ADLER32_INIT); + + for (uint32_t i = 0; i < 4; i++) + { + if (dst_ofs + 1 > dst_buf_size) + return 0; + + pDst[dst_ofs] = (uint8_t)(src_adler32 >> 24); + dst_ofs++; + + src_adler32 <<= 8; + } + + return dst_ofs; + } + + static void adjust_freq32(uint32_t num_freq, uint32_t* pFreq, uint16_t* pFreq16) + { + uint32_t total_freq = 0; + for (uint32_t i = 0; i < num_freq; i++) + total_freq += pFreq[i]; + + if (!total_freq) + { + memset(pFreq16, 0, num_freq * sizeof(uint16_t)); + return; + } + + uint32_t total_freq16 = 0; + for (uint32_t i = 0; i < num_freq; i++) + { + uint64_t f = pFreq[i]; + if (!f) + { + pFreq16[i] = 0; + continue; + } + + pFreq16[i] = (uint16_t)maximum(1, (uint32_t)((f * UINT16_MAX) / total_freq)); + + total_freq16 += pFreq16[i]; + } + + while (total_freq16 > UINT16_MAX) + { + total_freq16 = 0; + for (uint32_t i = 0; i < num_freq; i++) + { + if (pFreq[i]) + { + pFreq[i] = maximum(1, pFreq[i] >> 1); + total_freq16 += pFreq[i]; + } + } + } + } + +#if FPNG_TRAIN_HUFFMAN_TABLES + bool create_dynamic_block_prefix(uint64_t* pFreq, uint32_t num_chans, std::vector& prefix, uint64_t& bit_buf, int &bit_buf_size, uint32_t* pCodes, uint8_t* pCodesizes) + { + assert((num_chans == 3) || (num_chans == 4)); + assert(HUFF_COUNTS_SIZE == DEFL_MAX_HUFF_SYMBOLS_0); // must be equal + + defl_huff dh; + memset(&dh, 0, sizeof(dh)); + + uint32_t lit_freq[DEFL_MAX_HUFF_SYMBOLS_0]; + + uint32_t shift_len = 0; + for (; ; ) + { + uint32_t i; + for (i = 0; i < DEFL_MAX_HUFF_SYMBOLS_0; i++) + { + uint64_t f = pFreq[i]; + if (f) + f = maximum(1U, f >> shift_len); + + if (f > UINT32_MAX) + break; + + lit_freq[i] = (uint32_t)pFreq[i]; + } + + if (i == DEFL_MAX_HUFF_SYMBOLS_0) + break; + + shift_len++; + } + + // Ensure all valid Deflate literal/EOB/length syms are non-zero, so anything can be coded. + for (uint32_t i = 0; i <= 256; i++) + { + if (!lit_freq[i]) + lit_freq[i] = 1; + } + + for (uint32_t len = num_chans; len <= DEFL_MAX_MATCH_LEN; len += num_chans) + { + uint32_t sym = g_defl_len_sym[len - 3]; + if (!lit_freq[sym]) + lit_freq[sym] = 1; + } + + adjust_freq32(DEFL_MAX_HUFF_SYMBOLS_0, lit_freq, &dh.m_huff_count[0][0]); + + const uint32_t dist_sym = g_defl_small_dist_sym[num_chans - 1]; + dh.m_huff_count[1][dist_sym] = 1; + dh.m_huff_count[1][dist_sym + 1] = 1; // to workaround a bug in wuffs decoder + + prefix.resize(4096); + uint8_t* pDst = prefix.data(); + uint32_t dst_buf_size = (uint32_t)prefix.size(); + + uint32_t dst_ofs = 0; + + // zlib header + PUT_BITS(0x78, 8); + PUT_BITS(0x01, 8); + + // write BFINAL bit + PUT_BITS(1, 1); + + if (!defl_start_dynamic_block(&dh, pDst, dst_ofs, dst_buf_size, bit_buf, bit_buf_size)) + return false; + + prefix.resize(dst_ofs); + + for (uint32_t i = 0; i < DEFL_MAX_HUFF_SYMBOLS_0; i++) + { + pCodes[i] = dh.m_huff_codes[0][i]; + pCodesizes[i] = dh.m_huff_code_sizes[0][i]; + } + + return true; + } +#endif + + static uint32_t pixel_deflate_dyn_3_rle( + const uint8_t* pImg, uint32_t w, uint32_t h, + uint8_t* pDst, uint32_t dst_buf_size) + { + const uint32_t bpl = 1 + w * 3; + + uint64_t bit_buf = 0; + int bit_buf_size = 0; + + uint32_t dst_ofs = 0; + + // zlib header + PUT_BITS(0x78, 8); + PUT_BITS(0x01, 8); + + // write BFINAL bit + PUT_BITS(1, 1); + + std::vector codes((w + 1) * h); + uint32_t* pDst_codes = codes.data(); + + uint32_t lit_freq[DEFL_MAX_HUFF_SYMBOLS_0]; + memset(lit_freq, 0, sizeof(lit_freq)); + + const uint8_t* pSrc = pImg; + uint32_t src_ofs = 0; + + uint32_t src_adler32 = fpng_adler32(pImg, bpl * h, FPNG_ADLER32_INIT); + + const uint32_t dist_sym = g_defl_small_dist_sym[3 - 1]; + + for (uint32_t y = 0; y < h; y++) + { + const uint32_t end_src_ofs = src_ofs + bpl; + + const uint32_t filter_lit = pSrc[src_ofs++]; + *pDst_codes++ = 1 | (filter_lit << 8); + lit_freq[filter_lit]++; + + uint32_t prev_lits; + + { + uint32_t lits = READ_RGB_PIXEL(pSrc + src_ofs); + + *pDst_codes++ = lits << 8; + + lit_freq[lits & 0xFF]++; + lit_freq[(lits >> 8) & 0xFF]++; + lit_freq[lits >> 16]++; + + src_ofs += 3; + + prev_lits = lits; + } + + while (src_ofs < end_src_ofs) + { + uint32_t lits = READ_RGB_PIXEL(pSrc + src_ofs); + + if (lits == prev_lits) + { + uint32_t match_len = 3; + uint32_t max_match_len = minimum(255, (int)(end_src_ofs - src_ofs)); + + while (match_len < max_match_len) + { + if (READ_RGB_PIXEL(pSrc + src_ofs + match_len) != lits) + break; + match_len += 3; + } + + *pDst_codes++ = match_len - 1; + + uint32_t adj_match_len = match_len - 3; + + lit_freq[g_defl_len_sym[adj_match_len]]++; + + src_ofs += match_len; + } + else + { + *pDst_codes++ = lits << 8; + + lit_freq[lits & 0xFF]++; + lit_freq[(lits >> 8) & 0xFF]++; + lit_freq[lits >> 16]++; + + prev_lits = lits; + + src_ofs += 3; + } + + } // while (src_ofs < end_src_ofs) + + } // y + + assert(src_ofs == h * bpl); + const uint32_t total_codes = (uint32_t)(pDst_codes - codes.data()); + assert(total_codes <= codes.size()); + + defl_huff dh; + + lit_freq[256] = 1; + + adjust_freq32(DEFL_MAX_HUFF_SYMBOLS_0, lit_freq, &dh.m_huff_count[0][0]); + + memset(&dh.m_huff_count[1][0], 0, sizeof(dh.m_huff_count[1][0]) * DEFL_MAX_HUFF_SYMBOLS_1); + dh.m_huff_count[1][dist_sym] = 1; + dh.m_huff_count[1][dist_sym + 1] = 1; // to workaround a bug in wuffs decoder + + if (!defl_start_dynamic_block(&dh, pDst, dst_ofs, dst_buf_size, bit_buf, bit_buf_size)) + return 0; + + assert(bit_buf_size <= 7); + assert(dh.m_huff_codes[1][dist_sym] == 0 && dh.m_huff_code_sizes[1][dist_sym] == 1); + + for (uint32_t i = 0; i < total_codes; i++) + { + uint32_t c = codes[i]; + + uint32_t c_type = c & 0xFF; + if (c_type == 0) + { + uint32_t lits = c >> 8; + + PUT_BITS_CZ(dh.m_huff_codes[0][lits & 0xFF], dh.m_huff_code_sizes[0][lits & 0xFF]); + lits >>= 8; + + PUT_BITS_CZ(dh.m_huff_codes[0][lits & 0xFF], dh.m_huff_code_sizes[0][lits & 0xFF]); + lits >>= 8; + + PUT_BITS_CZ(dh.m_huff_codes[0][lits], dh.m_huff_code_sizes[0][lits]); + } + else if (c_type == 1) + { + uint32_t lit = c >> 8; + PUT_BITS_CZ(dh.m_huff_codes[0][lit], dh.m_huff_code_sizes[0][lit]); + } + else + { + uint32_t match_len = c_type + 1; + + uint32_t adj_match_len = match_len - 3; + + PUT_BITS_CZ(dh.m_huff_codes[0][g_defl_len_sym[adj_match_len]], dh.m_huff_code_sizes[0][g_defl_len_sym[adj_match_len]]); + PUT_BITS(adj_match_len & g_bitmasks[g_defl_len_extra[adj_match_len]], g_defl_len_extra[adj_match_len] + 1); // up to 6 bits, +1 for the match distance Huff code which is always 0 + + // no need to write the distance code, it's always 0 + //PUT_BITS_CZ(dh.m_huff_codes[1][dist_sym], dh.m_huff_code_sizes[1][dist_sym]); + } + + // up to 55 bits + PUT_BITS_FLUSH; + } + + PUT_BITS_CZ(dh.m_huff_codes[0][256], dh.m_huff_code_sizes[0][256]); + + PUT_BITS_FORCE_FLUSH; + + // Write zlib adler32 + for (uint32_t i = 0; i < 4; i++) + { + if ((dst_ofs + 1) > dst_buf_size) + return 0; + *(uint8_t*)(pDst + dst_ofs) = (uint8_t)(src_adler32 >> 24); + dst_ofs++; + + src_adler32 <<= 8; + } + + return dst_ofs; + } + + static uint32_t pixel_deflate_dyn_3_rle_one_pass( + const uint8_t* pImg, uint32_t w, uint32_t h, + uint8_t* pDst, uint32_t dst_buf_size) + { + const uint32_t bpl = 1 + w * 3; + + if (dst_buf_size < sizeof(g_dyn_huff_3)) + return false; + memcpy(pDst, g_dyn_huff_3, sizeof(g_dyn_huff_3)); + uint32_t dst_ofs = sizeof(g_dyn_huff_3); + + uint64_t bit_buf = DYN_HUFF_3_BITBUF; + int bit_buf_size = DYN_HUFF_3_BITBUF_SIZE; + + const uint8_t* pSrc = pImg; + uint32_t src_ofs = 0; + + uint32_t src_adler32 = fpng_adler32(pImg, bpl * h, FPNG_ADLER32_INIT); + + for (uint32_t y = 0; y < h; y++) + { + const uint32_t end_src_ofs = src_ofs + bpl; + + const uint32_t filter_lit = pSrc[src_ofs++]; + PUT_BITS_CZ(g_dyn_huff_3_codes[filter_lit].m_code, g_dyn_huff_3_codes[filter_lit].m_code_size); + + uint32_t prev_lits; + + { + uint32_t lits = READ_RGB_PIXEL(pSrc + src_ofs); + + PUT_BITS_CZ(g_dyn_huff_3_codes[lits & 0xFF].m_code, g_dyn_huff_3_codes[lits & 0xFF].m_code_size); + PUT_BITS_CZ(g_dyn_huff_3_codes[(lits >> 8) & 0xFF].m_code, g_dyn_huff_3_codes[(lits >> 8) & 0xFF].m_code_size); + PUT_BITS_CZ(g_dyn_huff_3_codes[(lits >> 16)].m_code, g_dyn_huff_3_codes[(lits >> 16)].m_code_size); + + src_ofs += 3; + + prev_lits = lits; + } + + PUT_BITS_FLUSH; + + while (src_ofs < end_src_ofs) + { + uint32_t lits = READ_RGB_PIXEL(pSrc + src_ofs); + + if (lits == prev_lits) + { + uint32_t match_len = 3; + uint32_t max_match_len = minimum(255, (int)(end_src_ofs - src_ofs)); + + while (match_len < max_match_len) + { + if (READ_RGB_PIXEL(pSrc + src_ofs + match_len) != lits) + break; + match_len += 3; + } + + uint32_t adj_match_len = match_len - 3; + + PUT_BITS_CZ(g_dyn_huff_3_codes[g_defl_len_sym[adj_match_len]].m_code, g_dyn_huff_3_codes[g_defl_len_sym[adj_match_len]].m_code_size); + PUT_BITS(adj_match_len & g_bitmasks[g_defl_len_extra[adj_match_len]], g_defl_len_extra[adj_match_len] + 1); // up to 6 bits, +1 for the match distance Huff code which is always 0 + + src_ofs += match_len; + } + else + { + PUT_BITS_CZ(g_dyn_huff_3_codes[lits & 0xFF].m_code, g_dyn_huff_3_codes[lits & 0xFF].m_code_size); + PUT_BITS_CZ(g_dyn_huff_3_codes[(lits >> 8) & 0xFF].m_code, g_dyn_huff_3_codes[(lits >> 8) & 0xFF].m_code_size); + PUT_BITS_CZ(g_dyn_huff_3_codes[(lits >> 16)].m_code, g_dyn_huff_3_codes[(lits >> 16)].m_code_size); + + prev_lits = lits; + + src_ofs += 3; + } + + PUT_BITS_FLUSH; + + } // while (src_ofs < end_src_ofs) + + } // y + + assert(src_ofs == h * bpl); + + assert(bit_buf_size <= 7); + + PUT_BITS_CZ(g_dyn_huff_3_codes[256].m_code, g_dyn_huff_3_codes[256].m_code_size); + + PUT_BITS_FORCE_FLUSH; + + // Write zlib adler32 + for (uint32_t i = 0; i < 4; i++) + { + if ((dst_ofs + 1) > dst_buf_size) + return 0; + *(uint8_t*)(pDst + dst_ofs) = (uint8_t)(src_adler32 >> 24); + dst_ofs++; + + src_adler32 <<= 8; + } + + return dst_ofs; + } + + static uint32_t pixel_deflate_dyn_4_rle( + const uint8_t* pImg, uint32_t w, uint32_t h, + uint8_t* pDst, uint32_t dst_buf_size) + { + const uint32_t bpl = 1 + w * 4; + + uint64_t bit_buf = 0; + int bit_buf_size = 0; + + uint32_t dst_ofs = 0; + + // zlib header + PUT_BITS(0x78, 8); + PUT_BITS(0x01, 8); + + // write BFINAL bit + PUT_BITS(1, 1); + + std::vector codes; + codes.resize((w + 1) * h); + uint64_t* pDst_codes = codes.data(); + + uint32_t lit_freq[DEFL_MAX_HUFF_SYMBOLS_0]; + memset(lit_freq, 0, sizeof(lit_freq)); + + const uint8_t* pSrc = pImg; + uint32_t src_ofs = 0; + + uint32_t src_adler32 = fpng_adler32(pImg, bpl * h, FPNG_ADLER32_INIT); + + const uint32_t dist_sym = g_defl_small_dist_sym[4 - 1]; + + for (uint32_t y = 0; y < h; y++) + { + const uint32_t end_src_ofs = src_ofs + bpl; + + const uint32_t filter_lit = pSrc[src_ofs++]; + *pDst_codes++ = 1 | (filter_lit << 8); + lit_freq[filter_lit]++; + + uint32_t prev_lits; + { + uint32_t lits = READ_LE32(pSrc + src_ofs); + + *pDst_codes++ = (uint64_t)lits << 8; + + lit_freq[lits & 0xFF]++; + lit_freq[(lits >> 8) & 0xFF]++; + lit_freq[(lits >> 16) & 0xFF]++; + lit_freq[lits >> 24]++; + + src_ofs += 4; + + prev_lits = lits; + } + + while (src_ofs < end_src_ofs) + { + uint32_t lits = READ_LE32(pSrc + src_ofs); + + if (lits == prev_lits) + { + uint32_t match_len = 4; + uint32_t max_match_len = minimum(252, (int)(end_src_ofs - src_ofs)); + + while (match_len < max_match_len) + { + if (READ_LE32(pSrc + src_ofs + match_len) != lits) + break; + match_len += 4; + } + + *pDst_codes++ = match_len - 1; + + uint32_t adj_match_len = match_len - 3; + + lit_freq[g_defl_len_sym[adj_match_len]]++; + + src_ofs += match_len; + } + else + { + *pDst_codes++ = (uint64_t)lits << 8; + + lit_freq[lits & 0xFF]++; + lit_freq[(lits >> 8) & 0xFF]++; + lit_freq[(lits >> 16) & 0xFF]++; + lit_freq[lits >> 24]++; + + prev_lits = lits; + + src_ofs += 4; + } + + } // while (src_ofs < end_src_ofs) + + } // y + + assert(src_ofs == h * bpl); + const uint32_t total_codes = (uint32_t)(pDst_codes - codes.data()); + assert(total_codes <= codes.size()); + + defl_huff dh; + + lit_freq[256] = 1; + + adjust_freq32(DEFL_MAX_HUFF_SYMBOLS_0, lit_freq, &dh.m_huff_count[0][0]); + + memset(&dh.m_huff_count[1][0], 0, sizeof(dh.m_huff_count[1][0]) * DEFL_MAX_HUFF_SYMBOLS_1); + dh.m_huff_count[1][dist_sym] = 1; + dh.m_huff_count[1][dist_sym + 1] = 1; // to workaround a bug in wuffs decoder + + if (!defl_start_dynamic_block(&dh, pDst, dst_ofs, dst_buf_size, bit_buf, bit_buf_size)) + return 0; + + assert(bit_buf_size <= 7); + assert(dh.m_huff_codes[1][dist_sym] == 0 && dh.m_huff_code_sizes[1][dist_sym] == 1); + + for (uint32_t i = 0; i < total_codes; i++) + { + uint64_t c = codes[i]; + + uint32_t c_type = (uint32_t)(c & 0xFF); + if (c_type == 0) + { + uint32_t lits = (uint32_t)(c >> 8); + + PUT_BITS_CZ(dh.m_huff_codes[0][lits & 0xFF], dh.m_huff_code_sizes[0][lits & 0xFF]); + lits >>= 8; + + PUT_BITS_CZ(dh.m_huff_codes[0][lits & 0xFF], dh.m_huff_code_sizes[0][lits & 0xFF]); + lits >>= 8; + + PUT_BITS_CZ(dh.m_huff_codes[0][lits & 0xFF], dh.m_huff_code_sizes[0][lits & 0xFF]); + lits >>= 8; + + if (bit_buf_size >= 49) + { + PUT_BITS_FLUSH; + } + + PUT_BITS_CZ(dh.m_huff_codes[0][lits], dh.m_huff_code_sizes[0][lits]); + } + else if (c_type == 1) + { + uint32_t lit = (uint32_t)(c >> 8); + PUT_BITS_CZ(dh.m_huff_codes[0][lit], dh.m_huff_code_sizes[0][lit]); + } + else + { + uint32_t match_len = c_type + 1; + + uint32_t adj_match_len = match_len - 3; + + PUT_BITS_CZ(dh.m_huff_codes[0][g_defl_len_sym[adj_match_len]], dh.m_huff_code_sizes[0][g_defl_len_sym[adj_match_len]]); + PUT_BITS(adj_match_len & g_bitmasks[g_defl_len_extra[adj_match_len]], g_defl_len_extra[adj_match_len] + 1); // up to 6 bits, +1 for the match distance Huff code which is always 0 + + // no need to write the distance code, it's always 0 + } + + // up to 55 bits + PUT_BITS_FLUSH; + } + + PUT_BITS_CZ(dh.m_huff_codes[0][256], dh.m_huff_code_sizes[0][256]); + + PUT_BITS_FORCE_FLUSH; + + // Write zlib adler32 + for (uint32_t i = 0; i < 4; i++) + { + if ((dst_ofs + 1) > dst_buf_size) + return 0; + *(uint8_t*)(pDst + dst_ofs) = (uint8_t)(src_adler32 >> 24); + dst_ofs++; + + src_adler32 <<= 8; + } + + return dst_ofs; + } + + static uint32_t pixel_deflate_dyn_4_rle_one_pass( + const uint8_t* pImg, uint32_t w, uint32_t h, + uint8_t* pDst, uint32_t dst_buf_size) + { + const uint32_t bpl = 1 + w * 4; + + if (dst_buf_size < sizeof(g_dyn_huff_4)) + return false; + memcpy(pDst, g_dyn_huff_4, sizeof(g_dyn_huff_4)); + uint32_t dst_ofs = sizeof(g_dyn_huff_4); + + uint64_t bit_buf = DYN_HUFF_4_BITBUF; + int bit_buf_size = DYN_HUFF_4_BITBUF_SIZE; + + const uint8_t* pSrc = pImg; + uint32_t src_ofs = 0; + + uint32_t src_adler32 = fpng_adler32(pImg, bpl * h, FPNG_ADLER32_INIT); + + for (uint32_t y = 0; y < h; y++) + { + const uint32_t end_src_ofs = src_ofs + bpl; + + const uint32_t filter_lit = pSrc[src_ofs++]; + PUT_BITS_CZ(g_dyn_huff_4_codes[filter_lit].m_code, g_dyn_huff_4_codes[filter_lit].m_code_size); + + PUT_BITS_FLUSH; + + uint32_t prev_lits; + { + uint32_t lits = READ_LE32(pSrc + src_ofs); + + PUT_BITS_CZ(g_dyn_huff_4_codes[lits & 0xFF].m_code, g_dyn_huff_4_codes[lits & 0xFF].m_code_size); + PUT_BITS_CZ(g_dyn_huff_4_codes[(lits >> 8) & 0xFF].m_code, g_dyn_huff_4_codes[(lits >> 8) & 0xFF].m_code_size); + PUT_BITS_CZ(g_dyn_huff_4_codes[(lits >> 16) & 0xFF].m_code, g_dyn_huff_4_codes[(lits >> 16) & 0xFF].m_code_size); + + if (bit_buf_size >= 49) + { + PUT_BITS_FLUSH; + } + + PUT_BITS_CZ(g_dyn_huff_4_codes[(lits >> 24)].m_code, g_dyn_huff_4_codes[(lits >> 24)].m_code_size); + + src_ofs += 4; + + prev_lits = lits; + } + + PUT_BITS_FLUSH; + + while (src_ofs < end_src_ofs) + { + uint32_t lits = READ_LE32(pSrc + src_ofs); + + if (lits == prev_lits) + { + uint32_t match_len = 4; + uint32_t max_match_len = minimum(252, (int)(end_src_ofs - src_ofs)); + + while (match_len < max_match_len) + { + if (READ_LE32(pSrc + src_ofs + match_len) != lits) + break; + match_len += 4; + } + + uint32_t adj_match_len = match_len - 3; + + const uint32_t match_code_bits = g_dyn_huff_4_codes[g_defl_len_sym[adj_match_len]].m_code_size; + const uint32_t len_extra_bits = g_defl_len_extra[adj_match_len]; + + if (match_len == 4) + { + // This check is optional - see if just encoding 4 literals would be cheaper than using a short match. + uint32_t lit_bits = g_dyn_huff_4_codes[lits & 0xFF].m_code_size + g_dyn_huff_4_codes[(lits >> 8) & 0xFF].m_code_size + + g_dyn_huff_4_codes[(lits >> 16) & 0xFF].m_code_size + g_dyn_huff_4_codes[(lits >> 24)].m_code_size; + + if ((match_code_bits + len_extra_bits + 1) > lit_bits) + goto do_literals; + } + + PUT_BITS_CZ(g_dyn_huff_4_codes[g_defl_len_sym[adj_match_len]].m_code, match_code_bits); + PUT_BITS(adj_match_len & g_bitmasks[g_defl_len_extra[adj_match_len]], len_extra_bits + 1); // up to 6 bits, +1 for the match distance Huff code which is always 0 + + src_ofs += match_len; + } + else + { +do_literals: + PUT_BITS_CZ(g_dyn_huff_4_codes[lits & 0xFF].m_code, g_dyn_huff_4_codes[lits & 0xFF].m_code_size); + PUT_BITS_CZ(g_dyn_huff_4_codes[(lits >> 8) & 0xFF].m_code, g_dyn_huff_4_codes[(lits >> 8) & 0xFF].m_code_size); + PUT_BITS_CZ(g_dyn_huff_4_codes[(lits >> 16) & 0xFF].m_code, g_dyn_huff_4_codes[(lits >> 16) & 0xFF].m_code_size); + + if (bit_buf_size >= 49) + { + PUT_BITS_FLUSH; + } + + PUT_BITS_CZ(g_dyn_huff_4_codes[(lits >> 24)].m_code, g_dyn_huff_4_codes[(lits >> 24)].m_code_size); + + src_ofs += 4; + + prev_lits = lits; + } + + PUT_BITS_FLUSH; + + } // while (src_ofs < end_src_ofs) + + } // y + + assert(src_ofs == h * bpl); + + assert(bit_buf_size <= 7); + + PUT_BITS_CZ(g_dyn_huff_4_codes[256].m_code, g_dyn_huff_4_codes[256].m_code_size); + + PUT_BITS_FORCE_FLUSH; + + // Write zlib adler32 + for (uint32_t i = 0; i < 4; i++) + { + if ((dst_ofs + 1) > dst_buf_size) + return 0; + *(uint8_t*)(pDst + dst_ofs) = (uint8_t)(src_adler32 >> 24); + dst_ofs++; + + src_adler32 <<= 8; + } + + return dst_ofs; + } + + static void vector_append(std::vector& buf, const void* pData, size_t len) + { + if (len) + { + size_t l = buf.size(); + buf.resize(l + len); + memcpy(buf.data() + l, pData, len); + } + } + + static void apply_filter(uint32_t filter, int w, int h, uint32_t num_chans, uint32_t bpl, const uint8_t* pSrc, const uint8_t* pPrev_src, uint8_t* pDst) + { + (void)h; + + switch (filter) + { + case 0: + { + *pDst++ = 0; + + memcpy(pDst, pSrc, bpl); + break; + } + case 2: + { + assert(pPrev_src); + + // Previous scanline + *pDst++ = 2; + +#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE + if (g_cpu_info.can_use_sse41()) + { + uint32_t bytes_to_process = w * num_chans, ofs = 0; + for (; bytes_to_process >= 16; bytes_to_process -= 16, ofs += 16) + _mm_storeu_si128((__m128i*)(pDst + ofs), _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(pSrc + ofs)), _mm_loadu_si128((const __m128i*)(pPrev_src + ofs)))); + + for (; bytes_to_process; bytes_to_process--, ofs++) + pDst[ofs] = (uint8_t)(pSrc[ofs] - pPrev_src[ofs]); + } + else +#endif + { + if (num_chans == 3) + { + for (uint32_t x = 0; x < (uint32_t)w; x++) + { + pDst[0] = (uint8_t)(pSrc[0] - pPrev_src[0]); + pDst[1] = (uint8_t)(pSrc[1] - pPrev_src[1]); + pDst[2] = (uint8_t)(pSrc[2] - pPrev_src[2]); + + pSrc += 3; + pPrev_src += 3; + pDst += 3; + } + } + else + { + for (uint32_t x = 0; x < (uint32_t)w; x++) + { + pDst[0] = (uint8_t)(pSrc[0] - pPrev_src[0]); + pDst[1] = (uint8_t)(pSrc[1] - pPrev_src[1]); + pDst[2] = (uint8_t)(pSrc[2] - pPrev_src[2]); + pDst[3] = (uint8_t)(pSrc[3] - pPrev_src[3]); + + pSrc += 4; + pPrev_src += 4; + pDst += 4; + } + } + } + + break; + } + default: + assert(0); + break; + } + } + + bool fpng_encode_image_to_memory(const void* pImage, uint32_t w, uint32_t h, uint32_t num_chans, std::vector& out_buf, uint32_t flags) + { + if (!endian_check()) + { + assert(0); + return false; + } + + if ((w < 1) || (h < 1) || (w * (uint64_t)h > UINT32_MAX) || (w > FPNG_MAX_SUPPORTED_DIM) || (h > FPNG_MAX_SUPPORTED_DIM)) + { + assert(0); + return false; + } + + if ((num_chans != 3) && (num_chans != 4)) + { + assert(0); + return false; + } + + int i, bpl = w * num_chans; + uint32_t y; + + std::vector temp_buf; + temp_buf.resize((bpl + 1) * h + 7); + uint32_t temp_buf_ofs = 0; + + for (y = 0; y < h; ++y) + { + const uint8_t* pSrc = (uint8_t*)pImage + y * bpl; + const uint8_t* pPrev_src = y ? ((uint8_t*)pImage + (y - 1) * bpl) : nullptr; + + uint8_t* pDst = &temp_buf[temp_buf_ofs]; + + apply_filter(y ? 2 : 0, w, h, num_chans, bpl, pSrc, pPrev_src, pDst); + + temp_buf_ofs += 1 + bpl; + } + + const uint32_t PNG_HEADER_SIZE = 58; + + uint32_t out_ofs = PNG_HEADER_SIZE; + + out_buf.resize((out_ofs + (bpl + 1) * h + 7) & ~7); + + uint32_t defl_size = 0; + if ((flags & FPNG_FORCE_UNCOMPRESSED) == 0) + { + if (num_chans == 3) + { + if (flags & FPNG_ENCODE_SLOWER) + defl_size = pixel_deflate_dyn_3_rle(temp_buf.data(), w, h, &out_buf[out_ofs], (uint32_t)out_buf.size() - out_ofs); + else + defl_size = pixel_deflate_dyn_3_rle_one_pass(temp_buf.data(), w, h, &out_buf[out_ofs], (uint32_t)out_buf.size() - out_ofs); + } + else + { + if (flags & FPNG_ENCODE_SLOWER) + defl_size = pixel_deflate_dyn_4_rle(temp_buf.data(), w, h, &out_buf[out_ofs], (uint32_t)out_buf.size() - out_ofs); + else + defl_size = pixel_deflate_dyn_4_rle_one_pass(temp_buf.data(), w, h, &out_buf[out_ofs], (uint32_t)out_buf.size() - out_ofs); + } + } + + uint32_t zlib_size = defl_size; + + if (!defl_size) + { + // Dynamic block failed to compress - fall back to uncompressed blocks, filter 0. + + temp_buf_ofs = 0; + + for (y = 0; y < h; ++y) + { + const uint8_t* pSrc = (uint8_t*)pImage + y * bpl; + + uint8_t* pDst = &temp_buf[temp_buf_ofs]; + + apply_filter(0, w, h, num_chans, bpl, pSrc, nullptr, pDst); + + temp_buf_ofs += 1 + bpl; + } + + assert(temp_buf_ofs <= temp_buf.size()); + + out_buf.resize(out_ofs + 6 + temp_buf_ofs + ((temp_buf_ofs + 65534) / 65535) * 5); + + uint32_t raw_size = write_raw_block(temp_buf.data(), (uint32_t)temp_buf_ofs, out_buf.data() + out_ofs, (uint32_t)out_buf.size() - out_ofs); + if (!raw_size) + { + // Somehow we miscomputed the size of the output buffer. + assert(0); + return false; + } + + zlib_size = raw_size; + } + + assert((out_ofs + zlib_size) <= out_buf.size()); + + out_buf.resize(out_ofs + zlib_size); + + const uint32_t idat_len = (uint32_t)out_buf.size() - PNG_HEADER_SIZE; + + // Write real PNG header, fdEC chunk, and the beginning of the IDAT chunk + { + static const uint8_t s_color_type[] = { 0x00, 0x00, 0x04, 0x02, 0x06 }; + + uint8_t pnghdr[58] = { + 0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a, // PNG sig + 0x00,0x00,0x00,0x0d, 'I','H','D','R', // IHDR chunk len, type + 0,0,(uint8_t)(w >> 8),(uint8_t)w, // width + 0,0,(uint8_t)(h >> 8),(uint8_t)h, // height + 8, //bit_depth + s_color_type[num_chans], // color_type + 0, // compression + 0, // filter + 0, // interlace + 0, 0, 0, 0, // IHDR crc32 + 0, 0, 0, 5, 'f', 'd', 'E', 'C', 82, 36, 147, 227, FPNG_FDEC_VERSION, 0xE5, 0xAB, 0x62, 0x99, // our custom private, ancillary, do not copy, fdEC chunk + (uint8_t)(idat_len >> 24),(uint8_t)(idat_len >> 16),(uint8_t)(idat_len >> 8),(uint8_t)idat_len, 'I','D','A','T' // IDATA chunk len, type + }; + + // Compute IHDR CRC32 + uint32_t c = (uint32_t)fpng_crc32(pnghdr + 12, 17, FPNG_CRC32_INIT); + for (i = 0; i < 4; ++i, c <<= 8) + ((uint8_t*)(pnghdr + 29))[i] = (uint8_t)(c >> 24); + + memcpy(out_buf.data(), pnghdr, PNG_HEADER_SIZE); + } + + // Write IDAT chunk's CRC32 and a 0 length IEND chunk + vector_append(out_buf, "\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16); // IDAT CRC32, followed by the IEND chunk + + // Compute IDAT crc32 + uint32_t c = (uint32_t)fpng_crc32(out_buf.data() + PNG_HEADER_SIZE - 4, idat_len + 4, FPNG_CRC32_INIT); + + for (i = 0; i < 4; ++i, c <<= 8) + (out_buf.data() + out_buf.size() - 16)[i] = (uint8_t)(c >> 24); + + return true; + } + +#ifndef FPNG_NO_STDIO + bool fpng_encode_image_to_file(const char* pFilename, const void* pImage, uint32_t w, uint32_t h, uint32_t num_chans, uint32_t flags) + { + std::vector out_buf; + if (!fpng_encode_image_to_memory(pImage, w, h, num_chans, out_buf, flags)) + return false; + + FILE* pFile = nullptr; +#ifdef _MSC_VER + fopen_s(&pFile, pFilename, "wb"); +#else + pFile = fopen(pFilename, "wb"); +#endif + if (!pFile) + return false; + + if (fwrite(out_buf.data(), 1, out_buf.size(), pFile) != out_buf.size()) + { + fclose(pFile); + return false; + } + + return (fclose(pFile) != EOF); + } +#endif + + // Decompression + + const uint32_t FPNG_DECODER_TABLE_BITS = 12; + const uint32_t FPNG_DECODER_TABLE_SIZE = 1 << FPNG_DECODER_TABLE_BITS; + + static bool build_decoder_table(uint32_t num_syms, uint8_t* pCode_sizes, uint32_t* pTable) + { + uint32_t num_codes[16]; + + memset(num_codes, 0, sizeof(num_codes)); + for (uint32_t i = 0; i < num_syms; i++) + { + assert(pCode_sizes[i] <= FPNG_DECODER_TABLE_SIZE); + num_codes[pCode_sizes[i]]++; + } + + uint32_t next_code[17]; + next_code[0] = next_code[1] = 0; + uint32_t total = 0; + for (uint32_t i = 1; i <= 15; i++) + next_code[i + 1] = (uint32_t)(total = ((total + ((uint32_t)num_codes[i])) << 1)); + + if (total != 0x10000) + { + uint32_t j = 0; + + for (uint32_t i = 15; i != 0; i--) + if ((j += num_codes[i]) > 1) + return false; + + if (j != 1) + return false; + } + + uint32_t rev_codes[DEFL_MAX_HUFF_SYMBOLS]; + + for (uint32_t i = 0; i < num_syms; i++) + rev_codes[i] = next_code[pCode_sizes[i]]++; + + memset(pTable, 0, sizeof(uint32_t) * FPNG_DECODER_TABLE_SIZE); + + for (uint32_t i = 0; i < num_syms; i++) + { + const uint32_t code_size = pCode_sizes[i]; + if (!code_size) + continue; + + uint32_t old_code = rev_codes[i], new_code = 0; + for (uint32_t j = code_size; j != 0; j--) + { + new_code = (new_code << 1) | (old_code & 1); + old_code >>= 1; + } + + uint32_t j = 1 << code_size; + + while (new_code < FPNG_DECODER_TABLE_SIZE) + { + pTable[new_code] = i | (code_size << 9); + new_code += j; + } + } + + return true; + } + + static const uint16_t g_run_len3_to_4[259] = + { + 0, + 0, 0, 4, 0, 0, 8, 0, 0, 12, 0, 0, 16, 0, 0, 20, 0, 0, 24, 0, 0, 28, 0, 0, + 32, 0, 0, 36, 0, 0, 40, 0, 0, 44, 0, 0, 48, 0, 0, 52, 0, 0, 56, 0, 0, + 60, 0, 0, 64, 0, 0, 68, 0, 0, 72, 0, 0, 76, 0, 0, 80, 0, 0, 84, 0, 0, + 88, 0, 0, 92, 0, 0, 96, 0, 0, 100, 0, 0, 104, 0, 0, 108, 0, 0, 112, 0, 0, + 116, 0, 0, 120, 0, 0, 124, 0, 0, 128, 0, 0, 132, 0, 0, 136, 0, 0, 140, 0, 0, + 144, 0, 0, 148, 0, 0, 152, 0, 0, 156, 0, 0, 160, 0, 0, 164, 0, 0, 168, 0, 0, + 172, 0, 0, 176, 0, 0, 180, 0, 0, 184, 0, 0, 188, 0, 0, 192, 0, 0, 196, 0, 0, + 200, 0, 0, 204, 0, 0, 208, 0, 0, 212, 0, 0, 216, 0, 0, 220, 0, 0, 224, 0, 0, + 228, 0, 0, 232, 0, 0, 236, 0, 0, 240, 0, 0, 244, 0, 0, 248, 0, 0, 252, 0, 0, + 256, 0, 0, 260, 0, 0, 264, 0, 0, 268, 0, 0, 272, 0, 0, 276, 0, 0, 280, 0, 0, + 284, 0, 0, 288, 0, 0, 292, 0, 0, 296, 0, 0, 300, 0, 0, 304, 0, 0, 308, 0, 0, + 312, 0, 0, 316, 0, 0, 320, 0, 0, 324, 0, 0, 328, 0, 0, 332, 0, 0, 336, 0, 0, + 340, 0, 0, + 344, + }; + + static const int s_length_extra[] = { 0,0,0,0, 0,0,0,0, 1,1,1,1, 2,2,2,2, 3,3,3,3, 4,4,4,4, 5,5,5,5, 0, 0,0 }; + static const int s_length_range[] = { 3,4,5,6, 7,8,9,10, 11,13,15,17, 19,23,27,31, 35,43,51,59, 67,83,99,115, 131,163,195,227, 258, 0,0 }; + +#define ENSURE_32BITS() do { \ + if (bit_buf_size < 32) { \ + if ((src_ofs + 4) > src_len) return false; \ + bit_buf |= ((uint64_t)READ_LE32(pSrc + src_ofs)) << bit_buf_size; \ + src_ofs += 4; bit_buf_size += 32; } \ + } while(0) + +#define GET_BITS(b, ll) do { \ + uint32_t l = ll; assert(l && (l <= 32)); \ + b = (uint32_t)(bit_buf & g_bitmasks[l]); \ + bit_buf >>= l; \ + bit_buf_size -= l; \ + ENSURE_32BITS(); \ + } while(0) + +#define SKIP_BITS(ll) do { \ + uint32_t l = ll; assert(l <= 32); \ + bit_buf >>= l; \ + bit_buf_size -= l; \ + ENSURE_32BITS(); \ + } while(0) + +#define GET_BITS_NE(b, ll) do { \ + uint32_t l = ll; assert(l && (l <= 32) && (bit_buf_size >= l)); \ + b = (uint32_t)(bit_buf & g_bitmasks[l]); \ + bit_buf >>= l; \ + bit_buf_size -= l; \ + } while(0) + +#define SKIP_BITS_NE(ll) do { \ + uint32_t l = ll; assert(l <= 32 && (bit_buf_size >= l)); \ + bit_buf >>= l; \ + bit_buf_size -= l; \ + } while(0) + + static bool prepare_dynamic_block( + const uint8_t* pSrc, uint32_t src_len, uint32_t& src_ofs, + uint32_t& bit_buf_size, uint64_t& bit_buf, + uint32_t* pLit_table, uint32_t num_chans) + { + static const uint8_t s_bit_length_order[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + + uint32_t num_lit_codes, num_dist_codes, num_clen_codes; + + GET_BITS(num_lit_codes, 5); + num_lit_codes += 257; + + GET_BITS(num_dist_codes, 5); + num_dist_codes += 1; + + uint32_t total_codes = num_lit_codes + num_dist_codes; + if (total_codes > (DEFL_MAX_HUFF_SYMBOLS_0 + DEFL_MAX_HUFF_SYMBOLS_1)) + return false; + + uint8_t code_sizes[DEFL_MAX_HUFF_SYMBOLS_0 + DEFL_MAX_HUFF_SYMBOLS_1]; + memset(code_sizes, 0, sizeof(code_sizes)); + + GET_BITS(num_clen_codes, 4); + num_clen_codes += 4; + + uint8_t clen_codesizes[DEFL_MAX_HUFF_SYMBOLS_2]; + memset(clen_codesizes, 0, sizeof(clen_codesizes)); + + for (uint32_t i = 0; i < num_clen_codes; i++) + { + uint32_t len = 0; + GET_BITS(len, 3); + clen_codesizes[s_bit_length_order[i]] = (uint8_t)len; + } + + uint32_t clen_table[FPNG_DECODER_TABLE_SIZE]; + if (!build_decoder_table(DEFL_MAX_HUFF_SYMBOLS_2, clen_codesizes, clen_table)) + return false; + + uint32_t min_code_size = 15; + + for (uint32_t cur_code = 0; cur_code < total_codes; ) + { + uint32_t sym = clen_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)]; + uint32_t sym_len = sym >> 9; + if (!sym_len) + return false; + SKIP_BITS(sym_len); + sym &= 511; + + if (sym <= 15) + { + // Can't be a fpng Huffman table + if (sym > FPNG_DECODER_TABLE_BITS) + return false; + + if (sym) + min_code_size = minimum(min_code_size, sym); + + code_sizes[cur_code++] = (uint8_t)sym; + continue; + } + + uint32_t rep_len = 0, rep_code_size = 0; + + switch (sym) + { + case 16: + { + GET_BITS(rep_len, 2); + rep_len += 3; + if (!cur_code) + return false; + rep_code_size = code_sizes[cur_code - 1]; + break; + } + case 17: + { + GET_BITS(rep_len, 3); + rep_len += 3; + rep_code_size = 0; + break; + } + case 18: + { + GET_BITS(rep_len, 7); + rep_len += 11; + rep_code_size = 0; + break; + } + } + + if ((cur_code + rep_len) > total_codes) + return false; + + for (; rep_len; rep_len--) + code_sizes[cur_code++] = (uint8_t)rep_code_size; + } + + uint8_t lit_codesizes[DEFL_MAX_HUFF_SYMBOLS_0]; + + memcpy(lit_codesizes, code_sizes, num_lit_codes); + memset(lit_codesizes + num_lit_codes, 0, DEFL_MAX_HUFF_SYMBOLS_0 - num_lit_codes); + + uint32_t total_valid_distcodes = 0; + for (uint32_t i = 0; i < num_dist_codes; i++) + total_valid_distcodes += (code_sizes[num_lit_codes + i] == 1); + + // 1 or 2 because the first version of FPNG only issued 1 valid distance code, but that upset wuffs. So we let 1 or 2 through. + if ((total_valid_distcodes < 1) || (total_valid_distcodes > 2)) + return false; + + if (code_sizes[num_lit_codes + (num_chans - 1)] != 1) + return false; + + if (total_valid_distcodes == 2) + { + // If there are two valid distance codes, make sure the first is 1 bit. + if (code_sizes[num_lit_codes + num_chans] != 1) + return false; + } + + if (!build_decoder_table(num_lit_codes, lit_codesizes, pLit_table)) + return false; + + // Add next symbol to decoder table, when it fits + for (uint32_t i = 0; i < FPNG_DECODER_TABLE_SIZE; i++) + { + uint32_t sym = pLit_table[i] & 511; + if (sym >= 256) + continue; + + uint32_t sym_bits = (pLit_table[i] >> 9) & 15; + if (!sym_bits) + continue; + assert(sym_bits <= FPNG_DECODER_TABLE_BITS); + + uint32_t bits_left = FPNG_DECODER_TABLE_BITS - sym_bits; + if (bits_left < min_code_size) + continue; + + uint32_t next_bits = i >> sym_bits; + uint32_t next_sym = pLit_table[next_bits] & 511; + uint32_t next_sym_bits = (pLit_table[next_bits] >> 9) & 15; + if ((!next_sym_bits) || (bits_left < next_sym_bits)) + continue; + + pLit_table[i] |= (next_sym << 16) | (next_sym_bits << (16 + 9)); + } + + return true; + } + + static bool fpng_pixel_zlib_raw_decompress( + const uint8_t* pSrc, uint32_t src_len, uint32_t zlib_len, + uint8_t* pDst, uint32_t w, uint32_t h, + uint32_t src_chans, uint32_t dst_chans) + { + assert((src_chans == 3) || (src_chans == 4)); + assert((dst_chans == 3) || (dst_chans == 4)); + + const uint32_t src_bpl = w * src_chans; + const uint32_t dst_bpl = w * dst_chans; + const uint32_t dst_len = dst_bpl * h; + + uint32_t src_ofs = 2; + uint32_t dst_ofs = 0; + uint32_t raster_ofs = 0; + uint32_t comp_ofs = 0; + + for (; ; ) + { + if ((src_ofs + 1) > src_len) + return false; + + const bool bfinal = (pSrc[src_ofs] & 1) != 0; + const uint32_t btype = (pSrc[src_ofs] >> 1) & 3; + if (btype != 0) + return false; + + src_ofs++; + + if ((src_ofs + 4) > src_len) + return false; + uint32_t len = pSrc[src_ofs + 0] | (pSrc[src_ofs + 1] << 8); + uint32_t nlen = pSrc[src_ofs + 2] | (pSrc[src_ofs + 3] << 8); + src_ofs += 4; + + if (len != (~nlen & 0xFFFF)) + return false; + + if ((src_ofs + len) > src_len) + return false; + + // Raw blocks are a relatively uncommon case so this isn't well optimized. + // Supports 3->4 and 4->3 byte/pixel conversion. + for (uint32_t i = 0; i < len; i++) + { + uint32_t c = pSrc[src_ofs + i]; + + if (!raster_ofs) + { + // Check filter type + if (c != 0) + return false; + + assert(!comp_ofs); + } + else + { + if (comp_ofs < dst_chans) + { + if (dst_ofs == dst_len) + return false; + + pDst[dst_ofs++] = (uint8_t)c; + } + + if (++comp_ofs == src_chans) + { + if (dst_chans > src_chans) + { + if (dst_ofs == dst_len) + return false; + + pDst[dst_ofs++] = (uint8_t)0xFF; + } + + comp_ofs = 0; + } + } + + if (++raster_ofs == (src_bpl + 1)) + { + assert(!comp_ofs); + raster_ofs = 0; + } + } + + src_ofs += len; + + if (bfinal) + break; + } + + if (comp_ofs != 0) + return false; + + // Check for zlib adler32 + if ((src_ofs + 4) != zlib_len) + return false; + + return (dst_ofs == dst_len); + } + + template + static bool fpng_pixel_zlib_decompress_3( + const uint8_t* pSrc, uint32_t src_len, uint32_t zlib_len, + uint8_t* pDst, uint32_t w, uint32_t h) + { + assert(src_len >= (zlib_len + 4)); + + const uint32_t dst_bpl = w * dst_comps; + //const uint32_t dst_len = dst_bpl * h; + + if (zlib_len < 7) + return false; + + // check zlib header + if ((pSrc[0] != 0x78) || (pSrc[1] != 0x01)) + return false; + + uint32_t src_ofs = 2; + + if ((pSrc[src_ofs] & 6) == 0) + return fpng_pixel_zlib_raw_decompress(pSrc, src_len, zlib_len, pDst, w, h, 3, dst_comps); + + if ((src_ofs + 4) > src_len) + return false; + uint64_t bit_buf = READ_LE32(pSrc + src_ofs); + src_ofs += 4; + + uint32_t bit_buf_size = 32; + + uint32_t bfinal, btype; + GET_BITS(bfinal, 1); + GET_BITS(btype, 2); + + // Must be the final block or it's not valid, and type=2 (dynamic) + if ((bfinal != 1) || (btype != 2)) + return false; + + uint32_t lit_table[FPNG_DECODER_TABLE_SIZE]; + if (!prepare_dynamic_block(pSrc, src_len, src_ofs, bit_buf_size, bit_buf, lit_table, 3)) + return false; + + const uint8_t* pPrev_scanline = nullptr; + uint8_t* pCur_scanline = pDst; + + for (uint32_t y = 0; y < h; y++) + { + // At start of PNG scanline, so read the filter literal + assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS); + uint32_t filter = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)]; + uint32_t filter_len = (filter >> 9) & 15; + if (!filter_len) + return false; + SKIP_BITS(filter_len); + filter &= 511; + + uint32_t expected_filter = (y ? 2 : 0); + if (filter != expected_filter) + return false; + + uint32_t x_ofs = 0; + uint8_t prev_delta_r = 0, prev_delta_g = 0, prev_delta_b = 0; + do + { + assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS); + uint32_t lit0_tab = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)]; + + uint32_t lit0 = lit0_tab; + uint32_t lit0_len = (lit0_tab >> 9) & 15; + if (!lit0_len) + return false; + SKIP_BITS(lit0_len); + + if (lit0 & 256) + { + lit0 &= 511; + + // Can't be EOB - we still have more pixels to decompress. + if (lit0 == 256) + return false; + + // Must be an RLE match against the previous pixel. + uint32_t run_len = s_length_range[lit0 - 257]; + if (lit0 >= 265) + { + uint32_t e; + GET_BITS_NE(e, s_length_extra[lit0 - 257]); + + run_len += e; + } + + // Skip match distance - it's always the same (3) + SKIP_BITS_NE(1); + + // Matches must always be a multiple of 3/4 bytes + assert((run_len % 3) == 0); + + if (dst_comps == 4) + { + const uint32_t x_ofs_end = x_ofs + g_run_len3_to_4[run_len]; + + // Check for valid run lengths + if (x_ofs == x_ofs_end) + return false; + + // Matches cannot cross scanlines. + if (x_ofs_end > dst_bpl) + return false; + + if (pPrev_scanline) + { + if ((prev_delta_r | prev_delta_g | prev_delta_b) == 0) + { + memcpy(pCur_scanline + x_ofs, pPrev_scanline + x_ofs, x_ofs_end - x_ofs); + x_ofs = x_ofs_end; + } + else + { + do + { + pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + prev_delta_r); + pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + prev_delta_g); + pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + prev_delta_b); + pCur_scanline[x_ofs + 3] = 0xFF; + x_ofs += 4; + } while (x_ofs < x_ofs_end); + } + } + else + { + do + { + pCur_scanline[x_ofs] = prev_delta_r; + pCur_scanline[x_ofs + 1] = prev_delta_g; + pCur_scanline[x_ofs + 2] = prev_delta_b; + pCur_scanline[x_ofs + 3] = 0xFF; + x_ofs += 4; + } while (x_ofs < x_ofs_end); + } + } + else + { + // Check for valid run lengths + if (!g_run_len3_to_4[run_len]) + return false; + + const uint32_t x_ofs_end = x_ofs + run_len; + + // Matches cannot cross scanlines. + if (x_ofs_end > dst_bpl) + return false; + + if (pPrev_scanline) + { + if ((prev_delta_r | prev_delta_g | prev_delta_b) == 0) + { + memcpy(pCur_scanline + x_ofs, pPrev_scanline + x_ofs, run_len); + x_ofs = x_ofs_end; + } + else + { + do + { + pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + prev_delta_r); + pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + prev_delta_g); + pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + prev_delta_b); + x_ofs += 3; + } while (x_ofs < x_ofs_end); + } + } + else + { + do + { + pCur_scanline[x_ofs] = prev_delta_r; + pCur_scanline[x_ofs + 1] = prev_delta_g; + pCur_scanline[x_ofs + 2] = prev_delta_b; + x_ofs += 3; + } while (x_ofs < x_ofs_end); + } + } + } + else + { + uint32_t lit1, lit2; + + uint32_t lit1_spec_len = (lit0_tab >> (16 + 9)); + uint32_t lit2_len; + if (lit1_spec_len) + { + lit1 = (lit0_tab >> 16) & 511; + SKIP_BITS_NE(lit1_spec_len); + + assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS); + lit2 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)]; + lit2_len = (lit2 >> 9) & 15; + if (!lit2_len) + return false; + } + else + { + assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS); + lit1 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)]; + uint32_t lit1_len = (lit1 >> 9) & 15; + if (!lit1_len) + return false; + SKIP_BITS_NE(lit1_len); + + lit2_len = (lit1 >> (16 + 9)); + if (lit2_len) + lit2 = lit1 >> 16; + else + { + assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS); + lit2 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)]; + lit2_len = (lit2 >> 9) & 15; + if (!lit2_len) + return false; + } + } + + SKIP_BITS(lit2_len); + + // Check for matches + if ((lit1 | lit2) & 256) + return false; + + if (dst_comps == 4) + { + if (pPrev_scanline) + { + pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + lit0); + pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + lit1); + pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + lit2); + pCur_scanline[x_ofs + 3] = 0xFF; + } + else + { + pCur_scanline[x_ofs] = (uint8_t)lit0; + pCur_scanline[x_ofs + 1] = (uint8_t)lit1; + pCur_scanline[x_ofs + 2] = (uint8_t)lit2; + pCur_scanline[x_ofs + 3] = 0xFF; + } + x_ofs += 4; + } + else + { + if (pPrev_scanline) + { + pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + lit0); + pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + lit1); + pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + lit2); + } + else + { + pCur_scanline[x_ofs] = (uint8_t)lit0; + pCur_scanline[x_ofs + 1] = (uint8_t)lit1; + pCur_scanline[x_ofs + 2] = (uint8_t)lit2; + } + x_ofs += 3; + } + + prev_delta_r = (uint8_t)lit0; + prev_delta_g = (uint8_t)lit1; + prev_delta_b = (uint8_t)lit2; + + // See if we can decode one more pixel. + uint32_t spec_next_len0_len = lit2 >> (16 + 9); + if ((spec_next_len0_len) && (x_ofs < dst_bpl)) + { + lit0 = (lit2 >> 16) & 511; + if (lit0 < 256) + { + SKIP_BITS_NE(spec_next_len0_len); + + assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS); + lit1 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)]; + uint32_t lit1_len = (lit1 >> 9) & 15; + if (!lit1_len) + return false; + SKIP_BITS(lit1_len); + + lit2_len = (lit1 >> (16 + 9)); + if (lit2_len) + lit2 = lit1 >> 16; + else + { + assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS); + lit2 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)]; + lit2_len = (lit2 >> 9) & 15; + if (!lit2_len) + return false; + } + + SKIP_BITS_NE(lit2_len); + + // Check for matches + if ((lit1 | lit2) & 256) + return false; + + if (dst_comps == 4) + { + if (pPrev_scanline) + { + pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + lit0); + pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + lit1); + pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + lit2); + pCur_scanline[x_ofs + 3] = 0xFF; + } + else + { + pCur_scanline[x_ofs] = (uint8_t)lit0; + pCur_scanline[x_ofs + 1] = (uint8_t)lit1; + pCur_scanline[x_ofs + 2] = (uint8_t)lit2; + pCur_scanline[x_ofs + 3] = 0xFF; + } + x_ofs += 4; + } + else + { + if (pPrev_scanline) + { + pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + lit0); + pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + lit1); + pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + lit2); + } + else + { + pCur_scanline[x_ofs] = (uint8_t)lit0; + pCur_scanline[x_ofs + 1] = (uint8_t)lit1; + pCur_scanline[x_ofs + 2] = (uint8_t)lit2; + } + x_ofs += 3; + } + + prev_delta_r = (uint8_t)lit0; + prev_delta_g = (uint8_t)lit1; + prev_delta_b = (uint8_t)lit2; + + } // if (lit0 < 256) + + } // if ((spec_next_len0_len) && (x_ofs < bpl)) + } + + } while (x_ofs < dst_bpl); + + pPrev_scanline = pCur_scanline; + pCur_scanline += dst_bpl; + + } // y + + // The last symbol should be EOB + assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS); + uint32_t lit0 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)]; + uint32_t lit0_len = (lit0 >> 9) & 15; + if (!lit0_len) + return false; + lit0 &= 511; + if (lit0 != 256) + return false; + + bit_buf_size -= lit0_len; + bit_buf >>= lit0_len; + + uint32_t align_bits = bit_buf_size & 7; + bit_buf_size -= align_bits; + bit_buf >>= align_bits; + + if (src_ofs < (bit_buf_size >> 3)) + return false; + src_ofs -= (bit_buf_size >> 3); + + // We should be at the very end, because the bit buf reads ahead 32-bits (which contains the zlib adler32). + if ((src_ofs + 4) != zlib_len) + return false; + + return true; + } + + template + static bool fpng_pixel_zlib_decompress_4( + const uint8_t* pSrc, uint32_t src_len, uint32_t zlib_len, + uint8_t* pDst, uint32_t w, uint32_t h) + { + assert(src_len >= (zlib_len + 4)); + + const uint32_t dst_bpl = w * dst_comps; + //const uint32_t dst_len = dst_bpl * h; + + if (zlib_len < 7) + return false; + + // check zlib header + if ((pSrc[0] != 0x78) || (pSrc[1] != 0x01)) + return false; + + uint32_t src_ofs = 2; + + if ((pSrc[src_ofs] & 6) == 0) + return fpng_pixel_zlib_raw_decompress(pSrc, src_len, zlib_len, pDst, w, h, 4, dst_comps); + + if ((src_ofs + 4) > src_len) + return false; + uint64_t bit_buf = READ_LE32(pSrc + src_ofs); + src_ofs += 4; + + uint32_t bit_buf_size = 32; + + uint32_t bfinal, btype; + GET_BITS(bfinal, 1); + GET_BITS(btype, 2); + + // Must be the final block or it's not valid, and type=2 (dynamic) + if ((bfinal != 1) || (btype != 2)) + return false; + + uint32_t lit_table[FPNG_DECODER_TABLE_SIZE]; + if (!prepare_dynamic_block(pSrc, src_len, src_ofs, bit_buf_size, bit_buf, lit_table, 4)) + return false; + + const uint8_t* pPrev_scanline = nullptr; + uint8_t* pCur_scanline = pDst; + + for (uint32_t y = 0; y < h; y++) + { + // At start of PNG scanline, so read the filter literal + assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS); + uint32_t filter = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)]; + uint32_t filter_len = (filter >> 9) & 15; + if (!filter_len) + return false; + SKIP_BITS(filter_len); + filter &= 511; + + uint32_t expected_filter = (y ? 2 : 0); + if (filter != expected_filter) + return false; + + uint32_t x_ofs = 0; + uint8_t prev_delta_r = 0, prev_delta_g = 0, prev_delta_b = 0, prev_delta_a = 0; + do + { + assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS); + uint32_t lit0_tab = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)]; + + uint32_t lit0 = lit0_tab; + uint32_t lit0_len = (lit0_tab >> 9) & 15; + if (!lit0_len) + return false; + SKIP_BITS(lit0_len); + + if (lit0 & 256) + { + lit0 &= 511; + + // Can't be EOB - we still have more pixels to decompress. + if (lit0 == 256) + return false; + + // Must be an RLE match against the previous pixel. + uint32_t run_len = s_length_range[lit0 - 257]; + if (lit0 >= 265) + { + uint32_t e; + GET_BITS_NE(e, s_length_extra[lit0 - 257]); + + run_len += e; + } + + // Skip match distance - it's always the same (4) + SKIP_BITS_NE(1); + + // Matches must always be a multiple of 3/4 bytes + if (run_len & 3) + return false; + + if (dst_comps == 3) + { + const uint32_t run_len3 = (run_len >> 2) * 3; + const uint32_t x_ofs_end = x_ofs + run_len3; + + // Matches cannot cross scanlines. + if (x_ofs_end > dst_bpl) + return false; + + if (pPrev_scanline) + { + if ((prev_delta_r | prev_delta_g | prev_delta_b | prev_delta_a) == 0) + { + memcpy(pCur_scanline + x_ofs, pPrev_scanline + x_ofs, run_len3); + x_ofs = x_ofs_end; + } + else + { + do + { + pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + prev_delta_r); + pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + prev_delta_g); + pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + prev_delta_b); + x_ofs += 3; + } while (x_ofs < x_ofs_end); + } + } + else + { + do + { + pCur_scanline[x_ofs] = prev_delta_r; + pCur_scanline[x_ofs + 1] = prev_delta_g; + pCur_scanline[x_ofs + 2] = prev_delta_b; + x_ofs += 3; + } while (x_ofs < x_ofs_end); + } + } + else + { + const uint32_t x_ofs_end = x_ofs + run_len; + + // Matches cannot cross scanlines. + if (x_ofs_end > dst_bpl) + return false; + + if (pPrev_scanline) + { + if ((prev_delta_r | prev_delta_g | prev_delta_b | prev_delta_a) == 0) + { + memcpy(pCur_scanline + x_ofs, pPrev_scanline + x_ofs, run_len); + x_ofs = x_ofs_end; + } + else + { + do + { + pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + prev_delta_r); + pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + prev_delta_g); + pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + prev_delta_b); + pCur_scanline[x_ofs + 3] = (uint8_t)(pPrev_scanline[x_ofs + 3] + prev_delta_a); + x_ofs += 4; + } while (x_ofs < x_ofs_end); + } + } + else + { + do + { + pCur_scanline[x_ofs] = prev_delta_r; + pCur_scanline[x_ofs + 1] = prev_delta_g; + pCur_scanline[x_ofs + 2] = prev_delta_b; + pCur_scanline[x_ofs + 3] = prev_delta_a; + x_ofs += 4; + } while (x_ofs < x_ofs_end); + } + } + } + else + { + uint32_t lit1, lit2; + + uint32_t lit1_spec_len = (lit0_tab >> (16 + 9)); + uint32_t lit2_len; + if (lit1_spec_len) + { + lit1 = (lit0_tab >> 16) & 511; + SKIP_BITS_NE(lit1_spec_len); + + assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS); + lit2 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)]; + lit2_len = (lit2 >> 9) & 15; + if (!lit2_len) + return false; + } + else + { + assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS); + lit1 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)]; + uint32_t lit1_len = (lit1 >> 9) & 15; + if (!lit1_len) + return false; + SKIP_BITS_NE(lit1_len); + + lit2_len = (lit1 >> (16 + 9)); + if (lit2_len) + lit2 = lit1 >> 16; + else + { + assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS); + lit2 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)]; + lit2_len = (lit2 >> 9) & 15; + if (!lit2_len) + return false; + } + } + + uint32_t lit3; + uint32_t lit3_len = lit2 >> (16 + 9); + + if (lit3_len) + { + lit3 = (lit2 >> 16); + SKIP_BITS(lit2_len + lit3_len); + } + else + { + SKIP_BITS(lit2_len); + + assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS); + lit3 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)]; + lit3_len = (lit3 >> 9) & 15; + if (!lit3_len) + return false; + + SKIP_BITS_NE(lit3_len); + } + + // Check for matches + if ((lit1 | lit2 | lit3) & 256) + return false; + + if (dst_comps == 3) + { + if (pPrev_scanline) + { + pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + lit0); + pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + lit1); + pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + lit2); + } + else + { + pCur_scanline[x_ofs] = (uint8_t)lit0; + pCur_scanline[x_ofs + 1] = (uint8_t)lit1; + pCur_scanline[x_ofs + 2] = (uint8_t)lit2; + } + + x_ofs += 3; + } + else + { + if (pPrev_scanline) + { + pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + lit0); + pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + lit1); + pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + lit2); + pCur_scanline[x_ofs + 3] = (uint8_t)(pPrev_scanline[x_ofs + 3] + lit3); + } + else + { + pCur_scanline[x_ofs] = (uint8_t)lit0; + pCur_scanline[x_ofs + 1] = (uint8_t)lit1; + pCur_scanline[x_ofs + 2] = (uint8_t)lit2; + pCur_scanline[x_ofs + 3] = (uint8_t)lit3; + } + + x_ofs += 4; + } + + prev_delta_r = (uint8_t)lit0; + prev_delta_g = (uint8_t)lit1; + prev_delta_b = (uint8_t)lit2; + prev_delta_a = (uint8_t)lit3; + } + + } while (x_ofs < dst_bpl); + + pPrev_scanline = pCur_scanline; + pCur_scanline += dst_bpl; + } // y + + // The last symbol should be EOB + assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS); + uint32_t lit0 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)]; + uint32_t lit0_len = (lit0 >> 9) & 15; + if (!lit0_len) + return false; + lit0 &= 511; + if (lit0 != 256) + return false; + + bit_buf_size -= lit0_len; + bit_buf >>= lit0_len; + + uint32_t align_bits = bit_buf_size & 7; + bit_buf_size -= align_bits; + bit_buf >>= align_bits; + + if (src_ofs < (bit_buf_size >> 3)) + return false; + src_ofs -= (bit_buf_size >> 3); + + // We should be at the very end, because the bit buf reads ahead 32-bits (which contains the zlib adler32). + if ((src_ofs + 4) != zlib_len) + return false; + + return true; + } + +#pragma pack(push) +#pragma pack(1) + struct png_chunk_prefix + { + uint32_t m_length; + uint8_t m_type[4]; + }; + struct png_ihdr + { + png_chunk_prefix m_prefix; + uint32_t m_width; + uint32_t m_height; + uint8_t m_bitdepth; + uint8_t m_color_type; + uint8_t m_comp_method; + uint8_t m_filter_method; + uint8_t m_interlace_method; + uint32_t m_crc32; + }; + const uint32_t IHDR_EXPECTED_LENGTH = 13; + struct png_iend + { + png_chunk_prefix m_prefix; + uint32_t m_crc32; + }; +#pragma pack(pop) + + static int fpng_get_info_internal(const void* pImage, uint32_t image_size, uint32_t& width, uint32_t& height, uint32_t& channels_in_file, uint32_t &idat_ofs, uint32_t &idat_len) + { + static const uint8_t s_png_sig[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; + + if (!endian_check()) + { + assert(0); + return false; + } + + width = 0; + height = 0; + channels_in_file = 0; + idat_ofs = 0, idat_len = 0; + + // Ensure the file has at least a minimum possible size + if (image_size < (sizeof(s_png_sig) + sizeof(png_ihdr) + sizeof(png_chunk_prefix) + 1 + sizeof(uint32_t) + sizeof(png_iend))) + return FPNG_DECODE_FAILED_NOT_PNG; + + if (memcmp(pImage, s_png_sig, 8) != 0) + return FPNG_DECODE_FAILED_NOT_PNG; + + const uint8_t* pImage_u8 = static_cast(pImage) + 8; + + const png_ihdr& ihdr = *reinterpret_cast(pImage_u8); + pImage_u8 += sizeof(png_ihdr); + + if (READ_BE32(&ihdr.m_prefix.m_length) != IHDR_EXPECTED_LENGTH) + return FPNG_DECODE_FAILED_NOT_PNG; + + if (fpng_crc32(ihdr.m_prefix.m_type, 4 + IHDR_EXPECTED_LENGTH, FPNG_CRC32_INIT) != READ_BE32(&ihdr.m_crc32)) + return FPNG_DECODE_FAILED_HEADER_CRC32; + + width = READ_BE32(&ihdr.m_width); + height = READ_BE32(&ihdr.m_height); + + if (!width || !height || (width > FPNG_MAX_SUPPORTED_DIM) || (height > FPNG_MAX_SUPPORTED_DIM)) + return FPNG_DECODE_FAILED_INVALID_DIMENSIONS; + + uint64_t total_pixels = (uint64_t)width * height; + if (total_pixels > (1 << 30)) + return FPNG_DECODE_FAILED_INVALID_DIMENSIONS; + + if ((ihdr.m_comp_method) || (ihdr.m_filter_method) || (ihdr.m_interlace_method) || (ihdr.m_bitdepth != 8)) + return FPNG_DECODE_NOT_FPNG; + + if (ihdr.m_color_type == 2) + channels_in_file = 3; + else if (ihdr.m_color_type == 6) + channels_in_file = 4; + + if (!channels_in_file) + return FPNG_DECODE_NOT_FPNG; + + // Scan all the chunks. Look for one IDAT, IEND, and our custom fdEC chunk that indicates the file was compressed by us. Skip any ancillary chunks. + bool found_fdec_chunk = false; + + for (; ; ) + { + const size_t src_ofs = pImage_u8 - static_cast(pImage); + if (src_ofs >= image_size) + return FPNG_DECODE_FAILED_CHUNK_PARSING; + + const uint32_t bytes_remaining = image_size - (uint32_t)src_ofs; + if (bytes_remaining < sizeof(uint32_t) * 3) + return FPNG_DECODE_FAILED_CHUNK_PARSING; + + const png_chunk_prefix* pChunk = reinterpret_cast(pImage_u8); + + const uint32_t chunk_len = READ_BE32(&pChunk->m_length); + if ((src_ofs + sizeof(uint32_t) + chunk_len + sizeof(uint32_t)) > image_size) + return FPNG_DECODE_FAILED_CHUNK_PARSING; + + for (uint32_t i = 0; i < 4; i++) + { + const uint8_t c = pChunk->m_type[i]; + const bool is_upper = (c >= 65) && (c <= 90), is_lower = (c >= 97) && (c <= 122); + if ((!is_upper) && (!is_lower)) + return FPNG_DECODE_FAILED_CHUNK_PARSING; + } + + const uint32_t expected_crc32 = READ_BE32(pImage_u8 + sizeof(uint32_t) * 2 + chunk_len); + + char chunk_type[5] = { (char)pChunk->m_type[0], (char)pChunk->m_type[1], (char)pChunk->m_type[2], (char)pChunk->m_type[3], 0 }; + const bool is_idat = strcmp(chunk_type, "IDAT") == 0; + +#if !FPNG_DISABLE_DECODE_CRC32_CHECKS + if (!is_idat) + { + uint32_t actual_crc32 = fpng_crc32(pImage_u8 + sizeof(uint32_t), sizeof(uint32_t) + chunk_len, FPNG_CRC32_INIT); + if (actual_crc32 != expected_crc32) + return FPNG_DECODE_FAILED_HEADER_CRC32; + } +#endif + + const uint8_t* pChunk_data = pImage_u8 + sizeof(uint32_t) * 2; + + if (strcmp(chunk_type, "IEND") == 0) + break; + else if (is_idat) + { + // If there were multiple IDAT's, or we didn't find the fdEC chunk, then it's not FPNG. + if ((idat_ofs) || (!found_fdec_chunk)) + return FPNG_DECODE_NOT_FPNG; + + idat_ofs = (uint32_t)src_ofs; + idat_len = chunk_len; + + // Sanity check the IDAT chunk length + if (idat_len < 7) + return FPNG_DECODE_FAILED_INVALID_IDAT; + } + else if (strcmp(chunk_type, "fdEC") == 0) + { + if (found_fdec_chunk) + return FPNG_DECODE_NOT_FPNG; + + // We've got our fdEC chunk. Now make sure it's big enough and check its contents. + if (chunk_len != 5) + return FPNG_DECODE_NOT_FPNG; + + // Check fdEC chunk sig + if ((pChunk_data[0] != 82) || (pChunk_data[1] != 36) || (pChunk_data[2] != 147) || (pChunk_data[3] != 227)) + return FPNG_DECODE_NOT_FPNG; + + // Check fdEC version + if (pChunk_data[4] != FPNG_FDEC_VERSION) + return FPNG_DECODE_NOT_FPNG; + + found_fdec_chunk = true; + } + else + { + // Bail if it's a critical chunk - can't be FPNG + if ((chunk_type[0] & 32) == 0) + return FPNG_DECODE_NOT_FPNG; + + // ancillary chunk - skip it + } + + pImage_u8 += sizeof(png_chunk_prefix) + chunk_len + sizeof(uint32_t); + } + + if ((!found_fdec_chunk) || (!idat_ofs)) + return FPNG_DECODE_NOT_FPNG; + + return FPNG_DECODE_SUCCESS; + } + + int fpng_get_info(const void* pImage, uint32_t image_size, uint32_t& width, uint32_t& height, uint32_t& channels_in_file) + { + uint32_t idat_ofs = 0, idat_len = 0; + return fpng_get_info_internal(pImage, image_size, width, height, channels_in_file, idat_ofs, idat_len); + } + + int fpng_decode_memory(const void *pImage, uint32_t image_size, std::vector &out, uint32_t& width, uint32_t& height, uint32_t &channels_in_file, uint32_t desired_channels) + { + out.resize(0); + width = 0; + height = 0; + channels_in_file = 0; + + if ((!pImage) || (!image_size) || ((desired_channels != 3) && (desired_channels != 4))) + { + assert(0); + return FPNG_DECODE_INVALID_ARG; + } + + uint32_t idat_ofs = 0, idat_len = 0; + int status = fpng_get_info_internal(pImage, image_size, width, height, channels_in_file, idat_ofs, idat_len); + if (status) + return status; + + const uint64_t mem_needed = (uint64_t)width * height * desired_channels; + if (mem_needed > UINT32_MAX) + return FPNG_DECODE_FAILED_DIMENSIONS_TOO_LARGE; + + // On 32-bit systems do a quick sanity check before we try to resize the output buffer. + if ((sizeof(size_t) == sizeof(uint32_t)) && (mem_needed >= 0x80000000)) + return FPNG_DECODE_FAILED_DIMENSIONS_TOO_LARGE; + + out.resize(mem_needed); + + const uint8_t* pIDAT_data = static_cast(pImage) + idat_ofs + sizeof(uint32_t) * 2; + const uint32_t src_len = image_size - (idat_ofs + sizeof(uint32_t) * 2); + + bool decomp_status; + if (desired_channels == 3) + { + if (channels_in_file == 3) + decomp_status = fpng_pixel_zlib_decompress_3<3>(pIDAT_data, src_len, idat_len, out.data(), width, height); + else + decomp_status = fpng_pixel_zlib_decompress_4<3>(pIDAT_data, src_len, idat_len, out.data(), width, height); + } + else + { + if (channels_in_file == 3) + decomp_status = fpng_pixel_zlib_decompress_3<4>(pIDAT_data, src_len, idat_len, out.data(), width, height); + else + decomp_status = fpng_pixel_zlib_decompress_4<4>(pIDAT_data, src_len, idat_len, out.data(), width, height); + } + if (!decomp_status) + { + // Something went wrong. Either the file data was corrupted, or it doesn't conform to one of our zlib/Deflate constraints. + // The conservative thing to do is indicate it wasn't written by us, and let the general purpose PNG decoder handle it. + return FPNG_DECODE_NOT_FPNG; + } + + return FPNG_DECODE_SUCCESS; + } + +#ifndef FPNG_NO_STDIO + int fpng_decode_file(const char* pFilename, std::vector& out, uint32_t& width, uint32_t& height, uint32_t& channels_in_file, uint32_t desired_channels) + { + FILE* pFile = nullptr; + +#ifdef _MSC_VER + fopen_s(&pFile, pFilename, "rb"); +#else + pFile = fopen(pFilename, "rb"); +#endif + + if (!pFile) + return FPNG_DECODE_FILE_OPEN_FAILED; + + if (fseek(pFile, 0, SEEK_END) != 0) + { + fclose(pFile); + return FPNG_DECODE_FILE_SEEK_FAILED; + } + +#ifdef _WIN32 + int64_t filesize = _ftelli64(pFile); +#else + int64_t filesize = ftello(pFile); +#endif + + if (fseek(pFile, 0, SEEK_SET) != 0) + { + fclose(pFile); + return FPNG_DECODE_FILE_SEEK_FAILED; + } + + if ( (filesize < 0) || (filesize > UINT32_MAX) || ( (sizeof(size_t) == sizeof(uint32_t)) && (filesize > 0x70000000) ) ) + { + fclose(pFile); + return FPNG_DECODE_FILE_TOO_LARGE; + } + + std::vector buf((size_t)filesize); + if (fread(buf.data(), 1, buf.size(), pFile) != buf.size()) + { + fclose(pFile); + return FPNG_DECODE_FILE_READ_FAILED; + } + + fclose(pFile); + + return fpng_decode_memory(buf.data(), (uint32_t)buf.size(), out, width, height, channels_in_file, desired_channels); + } +#endif + +} // namespace fpng + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to + + Richard Geldreich, Jr. + 12/30/2021 +*/ diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/fpng.h b/contrib/tinyusdz/tinyusdz_repo/src/external/fpng.h new file mode 100644 index 000000000..4d55e3afd --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/fpng.h @@ -0,0 +1,122 @@ +// fpng.h - unlicense (see end of fpng.cpp) +#pragma once + +#include +#include +#include + +#ifndef FPNG_TRAIN_HUFFMAN_TABLES + // Set to 1 when using the -t (training) option in fpng_test to generate new opaque/alpha Huffman tables for the single pass encoder. + #define FPNG_TRAIN_HUFFMAN_TABLES (0) +#endif + +namespace fpng +{ + // ---- Library initialization - call once to identify if the processor supports SSE. + // Otherwise you'll only get scalar fallbacks. + void fpng_init(); + + // ---- Useful Utilities + + // Returns true if the CPU supports SSE 4.1, and SSE support wasn't disabled by setting FPNG_NO_SSE=1. + // fpng_init() must have been called first, or it'll assert and return false. + bool fpng_cpu_supports_sse41(); + + // Fast CRC-32 SSE4.1+pclmul or a scalar fallback (slice by 4) + const uint32_t FPNG_CRC32_INIT = 0; + uint32_t fpng_crc32(const void* pData, size_t size, uint32_t prev_crc32 = FPNG_CRC32_INIT); + + // Fast Adler32 SSE4.1 Adler-32 with a scalar fallback. + const uint32_t FPNG_ADLER32_INIT = 1; + uint32_t fpng_adler32(const void* pData, size_t size, uint32_t adler = FPNG_ADLER32_INIT); + + // ---- Compression + enum + { + // Enables computing custom Huffman tables for each file, instead of using the custom global tables. + // Results in roughly 6% smaller files on average, but compression is around 40% slower. + FPNG_ENCODE_SLOWER = 1, + + // Only use raw Deflate blocks (no compression at all). Intended for testing. + FPNG_FORCE_UNCOMPRESSED = 2, + }; + + // Fast PNG encoding. The resulting file can be decoded either using a standard PNG decoder or by the fpng_decode_memory() function below. + // pImage: pointer to RGB or RGBA image pixels, R first in memory, B/A last. + // w/h - image dimensions. Image's row pitch in bytes must is w*num_chans. + // num_chans must be 3 or 4. + bool fpng_encode_image_to_memory(const void* pImage, uint32_t w, uint32_t h, uint32_t num_chans, std::vector& out_buf, uint32_t flags = 0); + +#ifndef FPNG_NO_STDIO + // Fast PNG encoding to the specified file. + bool fpng_encode_image_to_file(const char* pFilename, const void* pImage, uint32_t w, uint32_t h, uint32_t num_chans, uint32_t flags = 0); +#endif + + // ---- Decompression + + enum + { + FPNG_DECODE_SUCCESS = 0, // file is a valid PNG file and written by FPNG and the decode succeeded + + FPNG_DECODE_NOT_FPNG, // file is a valid PNG file, but it wasn't written by FPNG so you should try decoding it with a general purpose PNG decoder + + FPNG_DECODE_INVALID_ARG, // invalid function parameter + + FPNG_DECODE_FAILED_NOT_PNG, // file cannot be a PNG file + FPNG_DECODE_FAILED_HEADER_CRC32, // a chunk CRC32 check failed, file is likely corrupted or not PNG + FPNG_DECODE_FAILED_INVALID_DIMENSIONS, // invalid image dimensions in IHDR chunk (0 or too large) + FPNG_DECODE_FAILED_DIMENSIONS_TOO_LARGE, // decoding the file fully into memory would likely require too much memory (only on 32bpp builds) + FPNG_DECODE_FAILED_CHUNK_PARSING, // failed while parsing the chunk headers, or file is corrupted + FPNG_DECODE_FAILED_INVALID_IDAT, // IDAT data length is too small and cannot be valid, file is either corrupted or it's a bug + + // fpng_decode_file() specific errors + FPNG_DECODE_FILE_OPEN_FAILED, + FPNG_DECODE_FILE_TOO_LARGE, + FPNG_DECODE_FILE_READ_FAILED, + FPNG_DECODE_FILE_SEEK_FAILED + }; + + // Fast PNG decoding of files ONLY created by fpng_encode_image_to_memory() or fpng_encode_image_to_file(). + // If fpng_get_info() or fpng_decode_memory() returns FPNG_DECODE_NOT_FPNG, you should decode the PNG by falling back to a general purpose decoder. + // + // fpng_get_info() parses the PNG header and iterates through all chunks to determine if it's a file written by FPNG, but does not decompress the actual image data so it's relatively fast. + // + // pImage, image_size: Pointer to PNG image data and its size + // width, height: output image's dimensions + // channels_in_file: will be 3 or 4 + // + // Returns FPNG_DECODE_SUCCESS on success, otherwise one of the failure codes above. + // If FPNG_DECODE_NOT_FPNG is returned, you must decompress the file with a general purpose PNG decoder. + // If another error occurs, the file is likely corrupted or invalid, but you can still try to decompress the file with another decoder (which will likely fail). + int fpng_get_info(const void* pImage, uint32_t image_size, uint32_t& width, uint32_t& height, uint32_t& channels_in_file); + + // fpng_decode_memory() decompresses 24/32bpp PNG files ONLY encoded by this module. + // If the image was written by FPNG, it will decompress the image data, otherwise it will return FPNG_DECODE_NOT_FPNG in which case you should fall back to a general purpose PNG decoder (lodepng, stb_image, libpng, etc.) + // + // pImage, image_size: Pointer to PNG image data and its size + // out: Output 24/32bpp image buffer + // width, height: output image's dimensions + // channels_in_file: will be 3 or 4 + // desired_channels: must be 3 or 4 + // + // If the image is 24bpp and 32bpp is requested, the alpha values will be set to 0xFF. + // If the image is 32bpp and 24bpp is requested, the alpha values will be discarded. + // + // Returns FPNG_DECODE_SUCCESS on success, otherwise one of the failure codes above. + // If FPNG_DECODE_NOT_FPNG is returned, you must decompress the file with a general purpose PNG decoder. + // If another error occurs, the file is likely corrupted or invalid, but you can still try to decompress the file with another decoder (which will likely fail). + int fpng_decode_memory(const void* pImage, uint32_t image_size, std::vector& out, uint32_t& width, uint32_t& height, uint32_t& channels_in_file, uint32_t desired_channels); + +#ifndef FPNG_NO_STDIO + int fpng_decode_file(const char* pFilename, std::vector& out, uint32_t& width, uint32_t& height, uint32_t& channels_in_file, uint32_t desired_channels); +#endif + + // ---- Internal API used for Huffman table training purposes + +#if FPNG_TRAIN_HUFFMAN_TABLES + const uint32_t HUFF_COUNTS_SIZE = 288; + extern uint64_t g_huff_counts[HUFF_COUNTS_SIZE]; + bool create_dynamic_block_prefix(uint64_t* pFreq, uint32_t num_chans, std::vector& prefix, uint64_t& bit_buf, int& bit_buf_size, uint32_t *pCodes, uint8_t *pCodesizes); +#endif + +} // namespace fpng diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/glob/LICENSE b/contrib/tinyusdz/tinyusdz_repo/src/external/glob/LICENSE new file mode 100644 index 000000000..bcae1c6a2 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/glob/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Pranav + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/glob/README.md b/contrib/tinyusdz/tinyusdz_repo/src/external/glob/README.md new file mode 100644 index 000000000..30a48d5ab --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/glob/README.md @@ -0,0 +1,283 @@ +

+ +

+ +

+ Unix-style pathname pattern expansion +

+ +## Table of Contents + +- [Quick Start](#quick-start) + * [Build Library and Standalone Sample](#build-library-and-standalone-sample) +- [Usage](#usage) +- [API](#api) +- [Wildcards](#wildcards) +- [Examples](#examples) + * [Match file extensions](#match-file-extensions) + * [Match files in absolute pathnames](#match-files-in-absolute-pathnames) + * [Wildcards: Match a range of characters listed in brackets ('[]')](#wildcards-match-a-range-of-characters-listed-in-brackets-) + * [Exclude files from the matching](#exclude-files-from-the-matching) + * [Wildcards: Match any one character with question mark ('?')](#wildcards-match-any-one-character-with-question-mark-) + * [Case sensitivity](#case-sensitivity) + * [Tilde expansion](#tilde-expansion) +- [Contributing](#contributing) +- [License](#license) + +## Quick Start + +* This library is available in two flavors: + 1. Two file version: `glob.h` and `glob.cpp` + 2. Single header file version in `single_include/` +* No external dependencies - just the standard library +* Requires C++17 `std::filesystem` + - If you can't use `C++17`, you can integrate [gulrak/filesystem](https://github.com/gulrak/filesystem) with minimal effort. +* MIT License + +### Build Library and Standalone Sample + +```bash +cmake -Hall -Bbuild +cmake --build build + +# run standalone `glob` sample +./build/standalone/glob --help +``` + +### Usage + +```cpp +// Match on a single pattern +for (auto& p : glob::glob("~/.b*")) { // e.g., .bash_history, .bashrc + // do something with `p` +} + +// Match on multiple patterns +for (auto& p : glob::glob({"*.png", "*.jpg"})) { // e.g., foo.png, bar.jpg + // do something with `p` +} + +// Match recursively with `rglob` +for (auto& p : glob::rglob("**/*.hpp")) { // e.g., include/foo.hpp, include/foo/bar.hpp + // do something with `p` +} +``` + +## API + +```cpp +/// e.g., glob("*.hpp") +/// e.g., glob("**/*.cpp") +/// e.g., glob("test_files_02/[0-9].txt") +/// e.g., glob("/usr/local/include/nc*.h") +/// e.g., glob("test_files_02/?.txt") +vector glob(string pathname); + +/// Globs recursively +/// e.g., rglob("Documents/Projects/Foo/**/*.hpp") +/// e.g., rglob("test_files_02/*[0-9].txt") +vector rglob(string pathname); +``` + +There are also two convenience functions to `glob` on a list of patterns: + +```cpp +/// e.g., glob({"*.png", "*.jpg"}) +vector glob(vector pathnames); + +/// Globs recursively +/// e.g., rglob({"**/*.h", "**/*.hpp", "**/*.cpp"}) +vector rglob(vector pathnames); +``` + +## Wildcards + +| Wildcard | Matches | Example +|--- |--- |--- | +| `*` | any characters | `*.txt` matches all files with the txt extension | +| `?` | any one character | `???` matches files with 3 characters long | +| `[]` | any character listed in the brackets | `[ABC]*` matches files starting with A,B or C | +| `[-]` | any character in the range listed in brackets | `[A-Z]*` matches files starting with capital letters | +| `[!]` | any character not listed in the brackets | `[!ABC]*` matches files that do not start with A,B or C | + +## Examples + +The following examples use the [standalone](standalone/source/main.cpp) sample that is part of this repository to illustrate the library functionality. + +```console +foo@bar:~$ ./build/standalone/glob -h +Run glob to find all the pathnames matching a specified pattern +Usage: + ./build/standalone/glob [OPTION...] + + -h, --help Show help + -v, --version Print the current version number + -r, --recursive Run glob recursively + -i, --input arg Patterns to match +``` + +### Match file extensions + +```console +foo@bar:~$ tree +. +├── include +│   └── foo +│   ├── bar.hpp +│   ├── baz.hpp +│   └── foo.hpp +└── test + ├── bar.cpp + ├── doctest.hpp + ├── foo.cpp + └── main.cpp + +3 directories, 7 files + +foo@bar:~$ ./glob -i "**/*.hpp" +"test/doctest.hpp" + +foo@bar:~$ ./glob -i "**/**/*.hpp" +"include/foo/baz.hpp" +"include/foo/foo.hpp" +"include/foo/bar.hpp" +``` + +***NOTE*** If you run glob recursively, i.e., using `rglob`: + +```console +foo@bar:~$ ./glob -r -i "**/*.hpp" +"test/doctest.hpp" +"include/foo/baz.hpp" +"include/foo/foo.hpp" +"include/foo/bar.hpp" +``` + +### Match files in absolute pathnames + +```console +foo@bar:~$ ./glob -i '/usr/local/include/nc*.h' +"/usr/local/include/ncCheck.h" +"/usr/local/include/ncGroupAtt.h" +"/usr/local/include/ncUshort.h" +"/usr/local/include/ncByte.h" +"/usr/local/include/ncString.h" +"/usr/local/include/ncUint64.h" +"/usr/local/include/ncGroup.h" +"/usr/local/include/ncUbyte.h" +"/usr/local/include/ncvalues.h" +"/usr/local/include/ncInt.h" +"/usr/local/include/ncAtt.h" +"/usr/local/include/ncVar.h" +"/usr/local/include/ncUint.h" +``` + +### Wildcards: Match a range of characters listed in brackets ('[]') + +```console +foo@bar:~$ ls test_files_02 +1.txt 2.txt 3.txt 4.txt + +foo@bar:~$ ./glob -i 'test_files_02/[0-9].txt' +"test_files_02/4.txt" +"test_files_02/3.txt" +"test_files_02/2.txt" +"test_files_02/1.txt" + +foo@bar:~$ ./glob -i 'test_files_02/[1-2]*' +"test_files_02/2.txt" +"test_files_02/1.txt" +``` + +```console +foo@bar:~$ ls test_files_03 +file1.txt file2.txt file3.txt file4.txt + +foo@bar:~$ ./glob -i 'test_files_03/file[0-9].*' +"test_files_03/file2.txt" +"test_files_03/file3.txt" +"test_files_03/file1.txt" +"test_files_03/file4.txt" +``` + +### Exclude files from the matching + +```console +foo@bar:~$ ls test_files_01 +__init__.py bar.py foo.py + +foo@bar:~$ ./glob -i 'test_files_01/*[!__init__].py' +"test_files_01/bar.py" +"test_files_01/foo.py" + +foo@bar:~$ ./glob -i 'test_files_01/*[!__init__][!bar].py' +"test_files_01/foo.py" + +foo@bar:~$ ./glob -i 'test_files_01/[!_]*.py' +"test_files_01/bar.py" +"test_files_01/foo.py" +``` + +### Wildcards: Match any one character with question mark ('?') + +```console +foo@bar:~$ ls test_files_02 +1.txt 2.txt 3.txt 4.txt + +foo@bar:~$ ./glob -i 'test_files_02/?.txt' +"test_files_02/4.txt" +"test_files_02/3.txt" +"test_files_02/2.txt" +"test_files_02/1.txt" +``` + +```console +foo@bar:~$ ls test_files_03 +file1.txt file2.txt file3.txt file4.txt + +foo@bar:~$ ./glob -i 'test_files_03/????[3-4].txt' +"test_files_03/file3.txt" +"test_files_03/file4.txt" +``` + +### Case sensitivity + +`glob` matching is case-sensitive: + +```console +foo@bar:~$ ls test_files_05 +file1.png file2.png file3.PNG file4.PNG + +foo@bar:~$ ./glob -i 'test_files_05/*.png' +"test_files_05/file2.png" +"test_files_05/file1.png" + +foo@bar:~$ ./glob -i 'test_files_05/*.PNG' +"test_files_05/file3.PNG" +"test_files_05/file4.PNG" + +foo@bar:~$ ./glob -i "test_files_05/*.png","test_files_05/*.PNG" +"test_files_05/file2.png" +"test_files_05/file1.png" +"test_files_05/file3.PNG" +"test_files_05/file4.PNG" +``` + +### Tilde expansion + +```console +foo@bar:~$ ./glob -i "~/.b*" +"/Users/pranav/.bashrc" +"/Users/pranav/.bash_sessions" +"/Users/pranav/.bash_profile" +"/Users/pranav/.bash_history" + +foo@bar:~$ ./glob -i "~/Documents/Projects/glob/**/glob/*.h" +"/Users/pranav/Documents/Projects/glob/include/glob/glob.h" +``` + +## Contributing +Contributions are welcome, have a look at the [CONTRIBUTING.md](CONTRIBUTING.md) document for more information. + +## License +The project is available under the [MIT](https://opensource.org/licenses/MIT) license. diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/glob/include/glob/glob.h b/contrib/tinyusdz/tinyusdz_repo/src/external/glob/include/glob/glob.h new file mode 100644 index 000000000..2a7d1bffc --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/glob/include/glob/glob.h @@ -0,0 +1,40 @@ + +#pragma once +#include +#include +#include + +namespace glob { + +/// \param pathname string containing a path specification +/// \return vector of paths that match the pathname +/// +/// Pathnames can be absolute (/usr/src/Foo/Makefile) or relative (../../Tools/*/*.gif) +/// Pathnames can contain shell-style wildcards +/// Broken symlinks are included in the results (as in the shell) +std::vector glob(const std::string &pathname); + +/// \param pathnames string containing a path specification +/// \return vector of paths that match the pathname +/// +/// Globs recursively. +/// The pattern “**” will match any files and zero or more directories, subdirectories and +/// symbolic links to directories. +std::vector rglob(const std::string &pathname); + +/// Runs `glob` against each pathname in `pathnames` and accumulates the results +std::vector glob(const std::vector &pathnames); + +/// Runs `rglob` against each pathname in `pathnames` and accumulates the results +std::vector rglob(const std::vector &pathnames); + +/// Initializer list overload for convenience +std::vector glob(const std::initializer_list &pathnames); + +/// Initializer list overload for convenience +std::vector rglob(const std::initializer_list &pathnames); + +/// Returns true if the input path matche the glob pattern + bool fnmatch(const std::filesystem::path &name, const std::string &pattern); + +} // namespace glob diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/glob/single_include/glob/glob.hpp b/contrib/tinyusdz/tinyusdz_repo/src/external/glob/single_include/glob/glob.hpp new file mode 100644 index 000000000..93e1338d6 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/glob/single_include/glob/glob.hpp @@ -0,0 +1,448 @@ +// +// TinyUSDZ modification: +// - Disable exception +// - Use GHC filesystem +// +#pragma once +#include +// Assume ghc filesystem header is included(with NO_EXCEPTION version) +//#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 +namespace fs = std::filesystem; +#else +namespace fs = ghc::filesystem; +#endif + +namespace glob { + +namespace { + +static inline +bool string_replace(std::string &str, const std::string &from, const std::string &to) { + std::size_t start_pos = str.find(from); + if (start_pos == std::string::npos) + return false; + str.replace(start_pos, from.length(), to); + return true; +} + +static inline +std::string translate(const std::string &pattern) { + std::size_t i = 0, n = pattern.size(); + std::string result_string; + + while (i < n) { + auto c = pattern[i]; + i += 1; + if (c == '*') { + result_string += ".*"; + } else if (c == '?') { + result_string += "."; + } else if (c == '[') { + auto j = i; + if (j < n && pattern[j] == '!') { + j += 1; + } + if (j < n && pattern[j] == ']') { + j += 1; + } + while (j < n && pattern[j] != ']') { + j += 1; + } + if (j >= n) { + result_string += "\\["; + } else { + auto stuff = std::string(pattern.begin() + i, pattern.begin() + j); + if (stuff.find("--") == std::string::npos) { + string_replace(stuff, std::string{"\\"}, std::string{R"(\\)"}); + } else { + std::vector chunks; + std::size_t k = 0; + if (pattern[i] == '!') { + k = i + 2; + } else { + k = i + 1; + } + + while (true) { + k = pattern.find("-", k, j); + if (k == std::string::npos) { + break; + } + chunks.push_back(std::string(pattern.begin() + i, pattern.begin() + k)); + i = k + 1; + k = k + 3; + } + + chunks.push_back(std::string(pattern.begin() + i, pattern.begin() + j)); + // Escape backslashes and hyphens for set difference (--). + // Hyphens that create ranges shouldn't be escaped. + bool first = false; + for (auto &s : chunks) { + string_replace(s, std::string{"\\"}, std::string{R"(\\)"}); + string_replace(s, std::string{"-"}, std::string{R"(\-)"}); + if (first) { + stuff += s; + first = false; + } else { + stuff += "-" + s; + } + } + } + + // Escape set operations (&&, ~~ and ||). + std::string result; + std::regex_replace(std::back_inserter(result), // result + stuff.begin(), stuff.end(), // string + std::regex(std::string{R"([&~|])"}), // pattern + std::string{R"(\\\1)"}); // repl + stuff = result; + i = j + 1; + if (stuff[0] == '!') { + stuff = "^" + std::string(stuff.begin() + 1, stuff.end()); + } else if (stuff[0] == '^' || stuff[0] == '[') { + stuff = "\\\\" + stuff; + } + result_string = result_string + "[" + stuff + "]"; + } + } else { + // SPECIAL_CHARS + // closing ')', '}' and ']' + // '-' (a range in character set) + // '&', '~', (extended character set operations) + // '#' (comment) and WHITESPACE (ignored) in verbose mode + static std::string special_characters = "()[]{}?*+-|^$\\.&~# \t\n\r\v\f"; + static std::map special_characters_map; + if (special_characters_map.empty()) { + for (auto &sc : special_characters) { + special_characters_map.insert( + std::make_pair(static_cast(sc), std::string{"\\"} + std::string(1, sc))); + } + } + + if (special_characters.find(c) != std::string::npos) { + result_string += special_characters_map[static_cast(c)]; + } else { + result_string += c; + } + } + } + return std::string{"(("} + result_string + std::string{R"()|[\r\n])$)"}; +} + +static inline +std::regex compile_pattern(const std::string &pattern) { + return std::regex(translate(pattern), std::regex::ECMAScript); +} + +static inline +bool fnmatch(const fs::path &name, const std::string &pattern) { + return std::regex_match(name.string(), compile_pattern(pattern)); +} + +static inline +std::vector filter(const std::vector &names, + const std::string &pattern) { + // std::cout << "Pattern: " << pattern << "\n"; + std::vector result; + for (auto &name : names) { + // std::cout << "Checking for " << name.string() << "\n"; + if (fnmatch(name, pattern)) { + result.push_back(name); + } + } + return result; +} + +static inline +fs::path expand_tilde(fs::path path) { + if (path.empty()) return path; +#ifdef _WIN32 + char* home; + size_t sz; + _dupenv_s(&home, &sz, "USERPROFILE"); +#else + const char * home = std::getenv("HOME"); +#endif + if (home == nullptr) { + //throw std::invalid_argument("error: Unable to expand `~` - HOME environment variable not set."); + return path; + } + + std::string s = path.string(); + if (s[0] == '~') { + s = std::string(home) + s.substr(1, s.size() - 1); + return fs::path(s); + } else { + return path; + } +} + +static inline +bool has_magic(const std::string &pathname) { + static const auto magic_check = std::regex("([*?[])"); + return std::regex_search(pathname, magic_check); +} + +static inline +bool is_hidden(const std::string &pathname) { + return std::regex_match(pathname, std::regex("^(.*\\/)*\\.[^\\.\\/]+\\/*$")); +} + +static inline +bool is_recursive(const std::string &pattern) { return pattern == "**"; } + +static inline +std::vector iter_directory(const fs::path &dirname, bool dironly) { + std::vector result; + + std::error_code ec; + + auto current_directory = dirname; + if (current_directory.empty()) { + current_directory = fs::current_path(ec); + } + + if (fs::exists(current_directory, ec)) { +#if 0 + try { + for (auto &entry : fs::directory_iterator( + current_directory, fs::directory_options::follow_directory_symlink | + fs::directory_options::skip_permission_denied, ec)) { + if (!dironly || entry.is_directory()) { + if (dirname.is_absolute()) { + result.push_back(entry.path()); + } else { + result.push_back(fs::relative(entry.path())); + } + } + } + } catch (std::exception&) { + // not a directory + // do nothing + } +#else + auto it = fs::directory_iterator(current_directory, ec); + auto itE = fs::end(it); + for (; it != itE; it.increment(ec)) { + + if (ec) { + // TODO: Report error + continue; + } + + if (!dironly || it->is_directory(ec)) { + if (dirname.is_absolute()) { + result.push_back(it->path()); + } else { + result.push_back(fs::relative(it->path(), ec)); + } + } + } + +#endif + } + + return result; +} + +// Recursively yields relative pathnames inside a literal directory. +static inline +std::vector rlistdir(const fs::path &dirname, bool dironly) { + std::vector result; + auto names = iter_directory(dirname, dironly); + for (auto &x : names) { + if (!is_hidden(x.string())) { + result.push_back(x); + for (auto &y : rlistdir(x, dironly)) { + result.push_back(y); + } + } + } + return result; +} + +// This helper function recursively yields relative pathnames inside a literal +// directory. +static inline +std::vector glob2(const fs::path &dirname, const std::string &pattern, + bool dironly) { + (void)pattern; + // std::cout << "In glob2\n"; + std::vector result; + assert(is_recursive(pattern)); + for (auto &dir : rlistdir(dirname, dironly)) { + result.push_back(dir); + } + return result; +} + +// These 2 helper functions non-recursively glob inside a literal directory. +// They return a list of basenames. _glob1 accepts a pattern while _glob0 +// takes a literal basename (so it only has to check for its existence). +static inline +std::vector glob1(const fs::path &dirname, const std::string &pattern, + bool dironly) { + // std::cout << "In glob1\n"; + auto names = iter_directory(dirname, dironly); + std::vector filtered_names; + for (auto &n : names) { + if (!is_hidden(n.string())) { + filtered_names.push_back(n.filename()); + // if (n.is_relative()) { + // // std::cout << "Filtered (Relative): " << n << "\n"; + // filtered_names.push_back(fs::relative(n)); + // } else { + // // std::cout << "Filtered (Absolute): " << n << "\n"; + // filtered_names.push_back(n.filename()); + // } + } + } + return filter(filtered_names, pattern); +} + +static inline +std::vector glob0(const fs::path &dirname, const fs::path &basename, + bool /*dironly*/) { + // std::cout << "In glob0\n"; + std::vector result; + std::error_code ec; + if (basename.empty()) { + // 'q*x/' should match only directories. + if (fs::is_directory(dirname, ec)) { + result = {basename}; + } + } else { + if (fs::exists(dirname / basename, ec)) { + result = {basename}; + } + } + return result; +} + +static inline +std::vector glob(const std::string &pathname, bool recursive = false, + bool dironly = false) { + std::vector result; + + auto path = fs::path(pathname); + std::error_code ec; + + if (pathname[0] == '~') { + // expand tilde + path = expand_tilde(path); + } + + auto dirname = path.parent_path(); + const auto basename = path.filename(); + + if (!has_magic(pathname)) { + assert(!dironly); + if (!basename.empty()) { + if (fs::exists(path, ec)) { + result.push_back(path); + } + } else { + // Patterns ending with a slash should match only directories + if (fs::is_directory(dirname, ec)) { + result.push_back(path); + } + } + return result; + } + + if (dirname.empty()) { + if (recursive && is_recursive(basename.string())) { + return glob2(dirname, basename.string(), dironly); + } else { + return glob1(dirname, basename.string(), dironly); + } + } + + std::vector dirs; + if (dirname != fs::path(pathname) && has_magic(dirname.string())) { + dirs = glob(dirname.string(), recursive, true); + } else { + dirs = {dirname}; + } + + std::function(const fs::path &, const std::string &, bool)> + glob_in_dir; + if (has_magic(basename.string())) { + if (recursive && is_recursive(basename.string())) { + glob_in_dir = glob2; + } else { + glob_in_dir = glob1; + } + } else { + glob_in_dir = glob0; + } + + for (auto &d : dirs) { + for (auto &name : glob_in_dir(d, basename.string(), dironly)) { + fs::path subresult = name; + if (name.parent_path().empty()) { + subresult = d / name; + } + result.push_back(subresult); + } + } + + return result; +} + +} // namespace end + +static inline +std::vector glob(const std::string &pathname) { + return glob(pathname, false); +} + +static inline +std::vector rglob(const std::string &pathname) { + return glob(pathname, true); +} + +static inline +std::vector glob(const std::vector &pathnames) { + std::vector result; + for (auto &pathname : pathnames) { + for (auto &match : glob(pathname, false)) { + result.push_back(std::move(match)); + } + } + return result; +} + +static inline +std::vector rglob(const std::vector &pathnames) { + std::vector result; + for (auto &pathname : pathnames) { + for (auto &match : glob(pathname, true)) { + result.push_back(std::move(match)); + } + } + return result; +} + +static inline +std::vector +glob(const std::initializer_list &pathnames) { + return glob(std::vector(pathnames)); +} + +static inline +std::vector +rglob(const std::initializer_list &pathnames) { + return rglob(std::vector(pathnames)); +} + +} // namespace glob diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/glob/source/glob.cpp b/contrib/tinyusdz/tinyusdz_repo/src/external/glob/source/glob.cpp new file mode 100644 index 000000000..e48fe0911 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/glob/source/glob.cpp @@ -0,0 +1,381 @@ + +#include +#include +#include +#include +#include +#include +namespace fs = std::filesystem; + +namespace glob { + +namespace { + +bool string_replace(std::string &str, const std::string &from, const std::string &to) { + std::size_t start_pos = str.find(from); + if (start_pos == std::string::npos) + return false; + str.replace(start_pos, from.length(), to); + return true; +} + +std::string translate(const std::string &pattern) { + std::size_t i = 0, n = pattern.size(); + std::string result_string; + + while (i < n) { + auto c = pattern[i]; + i += 1; + if (c == '*') { + result_string += ".*"; + } else if (c == '?') { + result_string += "."; + } else if (c == '[') { + auto j = i; + if (j < n && pattern[j] == '!') { + j += 1; + } + if (j < n && pattern[j] == ']') { + j += 1; + } + while (j < n && pattern[j] != ']') { + j += 1; + } + if (j >= n) { + result_string += "\\["; + } else { + auto stuff = std::string(pattern.begin() + i, pattern.begin() + j); + if (stuff.find("--") == std::string::npos) { + string_replace(stuff, std::string{"\\"}, std::string{R"(\\)"}); + } else { + std::vector chunks; + std::size_t k = 0; + if (pattern[i] == '!') { + k = i + 2; + } else { + k = i + 1; + } + + while (true) { + k = pattern.find("-", k, j); + if (k == std::string::npos) { + break; + } + chunks.push_back(std::string(pattern.begin() + i, pattern.begin() + k)); + i = k + 1; + k = k + 3; + } + + chunks.push_back(std::string(pattern.begin() + i, pattern.begin() + j)); + // Escape backslashes and hyphens for set difference (--). + // Hyphens that create ranges shouldn't be escaped. + bool first = true; + for (auto &s : chunks) { + string_replace(s, std::string{"\\"}, std::string{R"(\\)"}); + string_replace(s, std::string{"-"}, std::string{R"(\-)"}); + if (first) { + stuff += s; + first = false; + } else { + stuff += "-" + s; + } + } + } + + // Escape set operations (&&, ~~ and ||). + std::string result; + std::regex_replace(std::back_inserter(result), // ressult + stuff.begin(), stuff.end(), // string + std::regex(std::string{R"([&~|])"}), // pattern + std::string{R"(\\\1)"}); // repl + stuff = result; + i = j + 1; + if (stuff[0] == '!') { + stuff = "^" + std::string(stuff.begin() + 1, stuff.end()); + } else if (stuff[0] == '^' || stuff[0] == '[') { + stuff = "\\\\" + stuff; + } + result_string = result_string + "[" + stuff + "]"; + } + } else { + // SPECIAL_CHARS + // closing ')', '}' and ']' + // '-' (a range in character set) + // '&', '~', (extended character set operations) + // '#' (comment) and WHITESPACE (ignored) in verbose mode + static std::string special_characters = "()[]{}?*+-|^$\\.&~# \t\n\r\v\f"; + static std::map special_characters_map; + if (special_characters_map.empty()) { + for (auto &sc : special_characters) { + special_characters_map.insert( + std::make_pair(static_cast(sc), std::string{"\\"} + std::string(1, sc))); + } + } + + if (special_characters.find(c) != std::string::npos) { + result_string += special_characters_map[static_cast(c)]; + } else { + result_string += c; + } + } + } + return std::string{"(("} + result_string + std::string{R"()|[\r\n])$)"}; +} + +std::regex compile_pattern(const std::string &pattern) { + return std::regex(translate(pattern), std::regex::ECMAScript); +} + +bool fnmatch(const fs::path &name, const std::string &pattern) { + return std::regex_match(name.string(), compile_pattern(pattern)); +} + +std::vector filter(const std::vector &names, + const std::string &pattern) { + // std::cout << "Pattern: " << pattern << "\n"; + std::vector result; + for (auto &name : names) { + // std::cout << "Checking for " << name.string() << "\n"; + if (fnmatch(name, pattern)) { + result.push_back(name); + } + } + return result; +} + +fs::path expand_tilde(fs::path path) { + if (path.empty()) return path; + + const char * home = std::getenv("HOME"); + if (home == nullptr) { + throw std::invalid_argument("error: Unable to expand `~` - HOME environment variable not set."); + } + + std::string s = path.string(); + if (s[0] == '~') { + s = std::string(home) + s.substr(1, s.size() - 1); + return fs::path(s); + } else { + return path; + } +} + +bool has_magic(const std::string &pathname) { + static const auto magic_check = std::regex("([*?[])"); + return std::regex_search(pathname, magic_check); +} + +bool is_hidden(const std::string &pathname) { return pathname[0] == '.'; } + +bool is_recursive(const std::string &pattern) { return pattern == "**"; } + +std::vector iter_directory(const fs::path &dirname, bool dironly) { + std::vector result; + + auto current_directory = dirname; + if (current_directory.empty()) { + current_directory = fs::current_path(); + } + + if (fs::exists(current_directory)) { + try { + for (auto &entry : fs::directory_iterator( + current_directory, fs::directory_options::follow_directory_symlink | + fs::directory_options::skip_permission_denied)) { + if (!dironly || entry.is_directory()) { + if (dirname.is_absolute()) { + result.push_back(entry.path()); + } else { + result.push_back(fs::relative(entry.path())); + } + } + } + } catch (std::exception&) { + // not a directory + // do nothing + } + } + + return result; +} + +// Recursively yields relative pathnames inside a literal directory. +std::vector rlistdir(const fs::path &dirname, bool dironly) { + std::vector result; + auto names = iter_directory(dirname, dironly); + for (auto &x : names) { + if (!is_hidden(x.string())) { + result.push_back(x); + for (auto &y : rlistdir(x, dironly)) { + result.push_back(y); + } + } + } + return result; +} + +// This helper function recursively yields relative pathnames inside a literal +// directory. +std::vector glob2(const fs::path &dirname, [[maybe_unused]] const fs::path &pattern, + bool dironly) { + // std::cout << "In glob2\n"; + std::vector result; + assert(is_recursive(pattern.string())); + for (auto &dir : rlistdir(dirname, dironly)) { + result.push_back(dir); + } + return result; +} + +// These 2 helper functions non-recursively glob inside a literal directory. +// They return a list of basenames. _glob1 accepts a pattern while _glob0 +// takes a literal basename (so it only has to check for its existence). + +std::vector glob1(const fs::path &dirname, const fs::path &pattern, + bool dironly) { + // std::cout << "In glob1\n"; + auto names = iter_directory(dirname, dironly); + std::vector filtered_names; + for (auto &n : names) { + if (!is_hidden(n.string())) { + filtered_names.push_back(n.filename()); + // if (n.is_relative()) { + // // std::cout << "Filtered (Relative): " << n << "\n"; + // filtered_names.push_back(fs::relative(n)); + // } else { + // // std::cout << "Filtered (Absolute): " << n << "\n"; + // filtered_names.push_back(n.filename()); + // } + } + } + return filter(filtered_names, pattern.string()); +} + +std::vector glob0(const fs::path &dirname, const fs::path &basename, + bool /*dironly*/) { + // std::cout << "In glob0\n"; + std::vector result; + if (basename.empty()) { + // 'q*x/' should match only directories. + if (fs::is_directory(dirname)) { + result = {basename}; + } + } else { + if (fs::exists(dirname / basename)) { + result = {basename}; + } + } + return result; +} + +std::vector glob(const fs::path &inpath, bool recursive = false, + bool dironly = false) { + std::vector result; + + const auto pathname = inpath.string(); + auto path = fs::path(pathname); + + if (pathname[0] == '~') { + // expand tilde + path = expand_tilde(path); + } + + auto dirname = path.parent_path(); + const auto basename = path.filename(); + + if (!has_magic(pathname)) { + assert(!dironly); + if (!basename.empty()) { + if (fs::exists(path)) { + result.push_back(path); + } + } else { + // Patterns ending with a slash should match only directories + if (fs::is_directory(dirname)) { + result.push_back(path); + } + } + return result; + } + + if (dirname.empty()) { + if (recursive && is_recursive(basename.string())) { + return glob2(dirname, basename, dironly); + } else { + return glob1(dirname, basename, dironly); + } + } + + std::vector dirs; + if (dirname != fs::path(pathname) && has_magic(dirname.string())) { + dirs = glob(dirname, recursive, true); + } else { + dirs = {dirname}; + } + + std::function(const fs::path &, const fs::path &, bool)> + glob_in_dir; + if (has_magic(basename.string())) { + if (recursive && is_recursive(basename.string())) { + glob_in_dir = glob2; + } else { + glob_in_dir = glob1; + } + } else { + glob_in_dir = glob0; + } + + for (auto &d : dirs) { + for (auto &name : glob_in_dir(d, basename, dironly)) { + fs::path subresult = name; + if (name.parent_path().empty()) { + subresult = d / name; + } + result.push_back(subresult); + } + } + + return result; +} + +} // namespace end + +std::vector glob(const std::string &pathname) { + return glob(pathname, false); +} + +std::vector rglob(const std::string &pathname) { + return glob(pathname, true); +} + +std::vector glob(const std::vector &pathnames) { + std::vector result; + for (auto &pathname : pathnames) { + for (auto &match : glob(pathname, false)) { + result.push_back(std::move(match)); + } + } + return result; +} + +std::vector rglob(const std::vector &pathnames) { + std::vector result; + for (auto &pathname : pathnames) { + for (auto &match : glob(pathname, true)) { + result.push_back(std::move(match)); + } + } + return result; +} + +std::vector +glob(const std::initializer_list &pathnames) { + return glob(std::vector(pathnames)); +} + +std::vector +rglob(const std::initializer_list &pathnames) { + return rglob(std::vector(pathnames)); +} + +} // namespace glob diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/half-edge.hh b/contrib/tinyusdz/tinyusdz_repo/src/external/half-edge.hh new file mode 100644 index 000000000..88e2404c2 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/half-edge.hh @@ -0,0 +1,384 @@ +/* +MIT License + +Copyright (c) 2019 Syoyo Fujita + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#ifndef TINYMESHUTILS_HALF_EDGE_HH_ +#define TINYMESHUTILS_HALF_EDGE_HH_ + +// TODO(syoyo): Support uint64_t for Edge. + +#include +#include +#include + +//#include // DBG + +// Using robin-map may give better performance +#if defined(TINYMESHUTILS_USE_ROBINMAP) +#error TODO +#include + +#else +#include + +// namespace umap = std::unordered_map; + +#endif + +namespace tinymeshutils { + +struct Edge { + Edge(const uint32_t _v0, const uint32_t _v1) : v0(_v0), v1(_v1) {} + + // create an 64 bit identifier that is unique for the not oriented edge + operator uint64_t() const { + uint32_t p0 = v0, p1 = v1; + if (p0 < p1) std::swap(p0, p1); + return (uint64_t(p0) << 32) | uint64_t(p1); + } + + uint32_t v0, v1; // start, end +}; + +struct HalfEdge { + // invalid or undefined = -1 + int64_t opposite_halfedge{-1}; // index to the halfedges array. + int64_t next_halfedge{-1}; // index to the halfedges array. + // int64_t vertex_index{-1}; // vertex index at the start of the edge + int64_t face_index{-1}; // index to face indices + int64_t edge_index{-1}; // index to edge indices +}; + +// Functor object for Edge +struct EdgeHash { + size_t operator()(const std::pair &k) const { + if (sizeof(size_t) == 8) { + // We can create unique value + return (uint64_t(k.first) << 32) | uint64_t(k.second); + } else { + // 32bit environment. we cannot create unique hash value. + return std::hash()(k.first) ^ std::hash()(k.second); + } + } +}; + +/// +/// Simple half-edge construction for polygons. +/// +/// @param[in] face_vert_indices Array of face vertex indices. +/// @param[in] face_vert_counts Array of the number of vertices for each face(3 +/// = triangle, 4 = quad, ...). +/// @param[out] edges Array of edges constructed +/// @param[out] halfedges Array of half-edges comstructed. length = +/// face_vert_indices.size() +/// @param[out] vertex_starting_halfedge_indices Index to starting half-edge in +/// `halfedges` for each vertex. -1 when no half-edge assigned to its vertex. +/// length = the highest value in `face_vert_indices`(= the number of vertices) +/// face_vert_indices.size() +/// @param[out] err Optional error message. +/// +/// @return true upon success. Return false when input mesh has invalid +/// topology. +/// +/// face_vert_indices.size() is equal to the sum of each elements in +/// `face_vert_counts`. Assume edge is defined in v0 -> v1, v1 -> v2, ... v(N-1) +/// -> v0 order. +/// +bool BuildHalfEdge(const std::vector &face_vert_indices, + const std::vector &face_vert_counts, + std::vector *edges, std::vector *halfedges, + std::vector *vertex_starting_halfedge_indices, + std::string *err = nullptr); + +} // namespace tinymeshutils + +#endif // TINYMESHUTILS_HALF_EDGE_HH_ + +#if defined(TINYMESHUTILS_HALF_EDGE_IMPLEMENTATION) +namespace tinymeshutils { + +bool BuildHalfEdge(const std::vector &face_vert_indices, + const std::vector &face_vert_counts, + std::vector *edges, std::vector *halfedges, + std::vector *vertex_starting_halfedge_indices, + std::string *err) { + // Based on documents at Internet and an implementation + // https://github.com/yig/halfedge + + size_t num_indices = 0; + for (size_t i = 0; i < face_vert_counts.size(); i++) { + if (face_vert_counts[i] < 3) { + // invalid # of vertices for a face. + return false; + } + num_indices += face_vert_counts[i]; + } + + // Find larget number = the number of vertices in input mesh. + uint32_t num_vertices = + (*std::max_element(face_vert_indices.begin(), face_vert_indices.end())) + + 1; + std::vector vertex_starting_halfedge_indices_buf(num_vertices, -1); + + // allocate buffer for half-edge. + std::vector halfedge_buf(num_indices); + + std::unordered_map, size_t, EdgeHash> + halfedge_table; // <, index to `half_edges`> + + // + // 1. Build edges. + // + std::vector edge_buf; // linear array of edges. + std::unordered_map + edge_map; // + + { + std::vector + tmp_edges; // linear array of edges. may have duplicated edges + + // list up and register edges. + size_t f_offset = 0; + for (size_t f = 0; f < face_vert_counts.size(); f++) { + uint32_t nv = face_vert_counts[f]; + if (nv < 3) { + if (err) { + (*err) = "Face " + std::to_string(f) + " has invalid # of vertices " + + std::to_string(nv) + "\n"; + } + return false; + } + + uint32_t ne = nv; + + // for each edge + for (size_t e = 0; e < size_t(ne); e++) { + // std::cout << "e = " << e << ", " << (e + 1) % nv << "\n"; + uint32_t v0 = face_vert_indices[f_offset + e]; + uint32_t v1 = face_vert_indices[f_offset + (e + 1) % nv]; + // std::cout << "v0 = " << v0 << ", v1 = " << v1 << "\n"; + + tmp_edges.push_back({v0, v1}); + } + + f_offset += nv; + } + + // create edge_map and unique array of edges. + for (const auto &edge : tmp_edges) { + uint64_t key = edge; + if (!edge_map.count(key)) { + size_t edge_idx = edge_buf.size(); + edge_map[edge] = edge_idx; + + edge_buf.push_back(edge); + } + } + } + +#if 0 + // dbg + for (size_t i = 0; i < edge_buf.size(); i++) { + std::cout << "edge[" << i << "] = " << edge_buf[i].v0 << ", " + << edge_buf[i].v1 << "\n"; + } +#endif + + // + // 2. Register half edges + // + { + size_t f_offset = 0; + for (size_t f = 0; f < face_vert_counts.size(); f++) { + // for each edge + uint32_t nv = face_vert_counts[f]; + if (nv < 3) { + if (err) { + (*err) = "Face " + std::to_string(f) + " has invalid # of vertices " + + std::to_string(nv) + "\n"; + } + return false; + } + + uint32_t ne = nv; + + // register + for (size_t e = 0; e < size_t(ne); e++) { + uint32_t v0 = face_vert_indices[f_offset + e]; + uint32_t v1 = face_vert_indices[f_offset + (e + 1) % nv]; + + Edge edge(v0, v1); + + // vertex pair must be unique over the input mesh + if (halfedge_table.count(std::make_pair(v0, v1))) { + if (err) { + (*err) = "(Register half edges). Invalid topology. Edge (v0: " + + std::to_string(v0) + ", v1: " + std::to_string(v1) + + ") must be unique but duplicated one exists for Face " + + std::to_string(f) + "\n"; + } + + return false; + } + + uint64_t eid = edge; // non oriented + + if (!edge_map.count(eid)) { + if (err) { + (*err) = "??? Edge (v0: " + std::to_string(edge.v0) + + ", v1: " + std::to_string(edge.v1) + + ") does not found for Face " + std::to_string(f) + + ". This should not be happen.\n"; + } + return false; + } + + size_t edge_index = edge_map[eid]; + + size_t halfedge_offset = f_offset + e; + + halfedge_table[std::make_pair(v0, v1)] = halfedge_offset; + + halfedge_buf[halfedge_offset].edge_index = int64_t(edge_index); + halfedge_buf[halfedge_offset].face_index = int64_t(f); + halfedge_buf[halfedge_offset].next_halfedge = + int64_t(f_offset + (e + 1) % nv); + + if (size_t(v0) >= vertex_starting_halfedge_indices_buf.size()) { + if (err) { + (*err) = + "Out-of-bounds access. v0 " + std::to_string(v0) + + " must be less than " + + std::to_string(vertex_starting_halfedge_indices_buf.size()) + + "\n"; + } + return false; + } + + if (vertex_starting_halfedge_indices_buf[size_t(v0)] == -1) { + // Set as starting half-edge + vertex_starting_halfedge_indices_buf[size_t(v0)] = + int64_t(halfedge_offset); + } + } + + f_offset += nv; + } + } + + // dbg + // for (size_t i = 0; i < halfedge_buf.size(); i++) { + // std::cout << "halfedge_buf[" << i << "].edge_index = " << + // halfedge_buf[i].edge_index << "\n"; + //} + + // + // 3. Find opposite half edges + // + for (size_t i = 0; i < halfedge_buf.size(); i++) { + HalfEdge &halfedge = halfedge_buf[i]; + if ((halfedge.edge_index == -1) || + (halfedge.edge_index >= int64_t(edge_buf.size()))) { + if (err) { + (*err) = "Invalid edge_index " + std::to_string(halfedge.edge_index) + + ". Must be >= 0 and < " + std::to_string(edge_buf.size()) + + "\n"; + } + + return false; + } + + const Edge &edge = edge_buf[size_t(halfedge.edge_index)]; + + if (halfedge_table.count(std::make_pair(edge.v1, edge.v0)) && + halfedge_table.count(std::make_pair(edge.v0, edge.v1))) { + // Opposite halfedge exists. Make a link. + + size_t halfedge_index0 = + halfedge_table.at(std::make_pair(edge.v0, edge.v1)); + + size_t halfedge_index1 = + halfedge_table.at(std::make_pair(edge.v1, edge.v0)); + + if (halfedge_index0 == halfedge_index1) { + if (err) { + (*err) = "Invalid halfedge_index. Both indices has same value.\n"; + } + return false; + } + + // Check if self-referencing. Choose different index compared to current + // index(`i`). + size_t opposite_halfedge_index = + (halfedge_index0 == i) ? halfedge_index1 : halfedge_index0; + + if (opposite_halfedge_index >= halfedge_buf.size()) { + if (err) { + (*err) = "Invalid halfedge_index " + + std::to_string(opposite_halfedge_index) + ". Must be < " + + std::to_string(halfedge_buf.size()) + "\n"; + } + return false; + } + + HalfEdge &opposite_halfedge = halfedge_buf[opposite_halfedge_index]; + + if (opposite_halfedge.edge_index != halfedge.edge_index) { + if (err) { + (*err) = "Edge id mismatch. opposite_halfedge.edge_index " + + std::to_string(opposite_halfedge.edge_index) + + " must be equal to halfedge.edge_index " + + std::to_string(halfedge.edge_index) + "\n"; + } + return false; + } + + // Make a link. + halfedge.opposite_halfedge = int64_t(opposite_halfedge_index); + } + } + + (*edges) = edge_buf; + (*halfedges) = halfedge_buf; + (*vertex_starting_halfedge_indices) = vertex_starting_halfedge_indices_buf; + +#if 0 + // dbg + std::cout << "halfedge_buf.size = " << halfedge_buf.size() << "\n"; + + for (size_t i = 0; i < halfedge_buf.size(); i++) { + std::cout << "halfedge[" << i << "]. face = " << halfedge_buf[i].face_index + << ", edge = " << halfedge_buf[i].edge_index + << ", opposite he = " << halfedge_buf[i].opposite_halfedge + << ", next he = " << halfedge_buf[i].next_halfedge << "\n"; + } + + for (size_t i = 0; i < vertex_starting_halfedge_indices_buf.size(); i++) { + std::cout << "v[" << i << "].halfedge_index = " << vertex_starting_halfedge_indices_buf[i] << "\n"; + } +#endif + + return true; +} + +} // namespace tinymeshutils +#endif // TINYMESHUTILS_HALF_EDGE_IMPLEMENTATION diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/jsteemann/LICENSE b/contrib/tinyusdz/tinyusdz_repo/src/external/jsteemann/LICENSE new file mode 100644 index 000000000..9de9747f6 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/jsteemann/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015 ArangoDB GmbH + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/jsteemann/README.md b/contrib/tinyusdz/tinyusdz_repo/src/external/jsteemann/README.md new file mode 100644 index 000000000..56b974385 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/jsteemann/README.md @@ -0,0 +1,196 @@ +String to integer conversion library +------------------------------------ + +This is a header-only library for converting a string containing a +base-10 number into an integer value of a configurable type T. + +The library offers functions to parse a string into an integer with +validation of the input string or without validation. It is up to the +embedder to pick the appropriate function. + +If the validating functions are used, the input string is considered +valid only if it consists of the digits '0' to '9'. An optional '+' +or '-' sign is allowed at the very beginning of the input string too. +If any other character is found, the input is considered invalid. +The input string does not need to be null-terminated. + +If the parsed number value would be less or greater than what the +number type T can store without truncation, the input is considered +invalid and parsing is stopped. + +The non-validating functions do not validate the input string for +validity nor do they check for overflow or underflow of the result +value. + +The library makes a few assumptions about the input in order to provide +good performance: + +* all input is treated as base-10 numbers - no support for hexadecimal + or octal numbers nor for floating point values +* the library functions are optimized for valid input, i.e. strings that + contain only the digits '0' to '9' (with an optional '+' or '-' sign in + front). This is also true for the validating functions +* the library will not handle leading whitespace in the input string. + input strings with leading or trailing whitespace are simply considered + invalid. The same is true for input strings containing non-integer + numbers +* the library functions will not modify `errno` in any way, nor will they + throw any exceptions +* the library functions will not allocate any memory on the heap + +In contrast to other common string-to-integer functions, the functions +of this library do not require null-terminated input strings. An input +string is delimited simply by a start pointer (`char const* p`) and an end +pointer (`char const* e`) into its data. All library functions guarantee +to only read memory between `p` (inclusive) and `e` (exclusive). + +Use cases +--------- + +This library's string-to-integer conversion functionality is not as flexible +as the one provided by other string-to-integer functions, e.g. `std::stoull` +from the standard library or `std::strtoull`. + +This library sacrifices some of the generality for performance. It is also +optimized for valid input strings, and provides special functions that do not +validate the input at all. Embedders can use these functions when they know +the input strings are valid and will not overflow the target datatype. + +Example usage +------------- + +```cpp +#include "jsteemann/atoi.h" +#include + +// the string to be parsed +std::string value("12345678901234"); + +bool valid; +auto result = jsteemann::atoi(value.data(), value.data() + value.size(), valid); + +if (valid) { + // parsing succeeded! + std::cout << "successfully parsed '" << value << "' into number " << result << std::endl; +} else { + // parsing failed! + std::cout << "failed to parse '" << value << "' into a number!" << std::endl; +} +``` + +The library contains the following validating functions: +```cpp +// function to convert the string value between p +// (inclusive) and e (exclusive) into a number value of type T +// +// the input string will always be interpreted as a base-10 number. +// expects the input string to contain only the digits '0' to '9'. an +// optional '+' or '-' sign is allowed too. +// if any other character is found, the output parameter "valid" will +// be set to false. if the parsed value is less or greater than what +// type T can store without truncation, "valid" will also be set to +// false. In this case the returned result should not be used. +// this function will not modify errno. +template +static inline T atoi(char const* p, char const* e, bool& valid) noexcept; + +// low-level worker function to convert the string value between p +// (inclusive) and e (exclusive) into a positive number value of type T +// +// the input string will always be interpreted as a base-10 number. +// expects the input string to contain only the digits '0' to '9'. +// if any other character is found, the output parameter "valid" will +// be set to false. if the parsed value is greater than what type T can +// store without truncation, "valid" will also be set to false. In this +// case the returned result should not be used. +// this function will not modify errno. +template +static inline T atoi_positive(char const* p, char const* e, bool& valid) noexcept; +``` + +The library contains the following non-validating functions: +```cpp +// function to convert the string value between p +// (inclusive) and e (exclusive) into a number value of type T, without +// validation of the input string - use this only for trusted input! +// +// the input string will always be interpreted as a base-10 number. +// expects the input string to contain only the digits '0' to '9'. an +// optional '+' or '-' sign is allowed too. +// there is no validation of the input string, and overflow or underflow +// of the result value will not be detected. +// this function will not modify errno. +template +inline T atoi_unchecked(char const* p, char const* e) noexcept; + +// low-level worker function to convert the string value between p +// (inclusive) and e (exclusive) into a positive number value of type T, +// without validation of the input string - use this only for trusted input! +// +// the input string will always be interpreted as a base-10 number. +// expects the input string to contain only the digits '0' to '9'. +// there is no validation of the input string, and overflow or underflow +// of the result value will not be detected. +// this function will not modify errno. +template +inline T atoi_positive_unchecked(char const* p, char const* e) noexcept; +``` + +Benchmark +--------- + +To compare the performance of this library and the standard library's +`std::stoull` and `std::strtoull` functions, there is a benchmark executable +included. + +It can be built and run as follows: +```bash +mkdir -p build +# be sure to build in Release mode here for compiler optimizations +(cd build && cmake -DCMAKE_BUILD_TYPE=Release ..) +build/benchmark/bench +``` + +Benchmark results from local laptop (Linux x86-64): +``` +500000000 iterations of std::stoull, string '7' took 4792 ms +500000000 iterations of std::strtoull, string '7' took 4482 ms +500000000 iterations of jsteemann::atoi, string '7' took 1027 ms +500000000 iterations of jsteemann::atoi_positive, string '7' took 870 ms +500000000 iterations of jsteemann::atoi_positive_unchecked, string '7' took 873 ms + +500000000 iterations of std::stoull, string '874' took 6495 ms +500000000 iterations of std::strtoull, string '874' took 6241 ms +500000000 iterations of jsteemann::atoi, string '874' took 2268 ms +500000000 iterations of jsteemann::atoi_positive, string '874' took 2222 ms +500000000 iterations of jsteemann::atoi_positive_unchecked, string '874' took 1092 ms + +500000000 iterations of std::stoull, string '123456' took 9172 ms +500000000 iterations of std::strtoull, string '123456' took 8887 ms +500000000 iterations of jsteemann::atoi, string '123456' took 3945 ms +500000000 iterations of jsteemann::atoi_positive, string '123456' took 3883 ms +500000000 iterations of jsteemann::atoi_positive_unchecked, string '123456' took 1956 ms + +500000000 iterations of std::stoull, string '12345654666646' took 16413 ms +500000000 iterations of std::strtoull, string '12345654666646' took 16026 ms +500000000 iterations of jsteemann::atoi, string '12345654666646' took 9061 ms +500000000 iterations of jsteemann::atoi_positive, string '12345654666646' took 8527 ms +500000000 iterations of jsteemann::atoi_positive_unchecked, string '12345654666646' took 4154 ms + +500000000 iterations of std::stoull, string '16323949897939569634' took 21772 ms +500000000 iterations of std::strtoull, string '16323949897939569634' took 21537 ms +500000000 iterations of jsteemann::atoi, string '16323949897939569634' took 16677 ms +500000000 iterations of jsteemann::atoi_positive, string '16323949897939569634' took 15597 ms +500000000 iterations of jsteemann::atoi_positive_unchecked, string '16323949897939569634' took 6203 ms +``` + +Tests +----- + +To run the library's tests locally, execute the following commands: + +```bash +mkdir -p build +(cd build && cmake ..) +build/tests/tests +``` diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/jsteemann/atoi.h b/contrib/tinyusdz/tinyusdz_repo/src/external/jsteemann/atoi.h new file mode 100644 index 000000000..b53faa52f --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/jsteemann/atoi.h @@ -0,0 +1,250 @@ +// Based on https://github.com/jsteemann/atoi +// Modified to return error codes +#ifndef JSTEEMANN_ATOI_H +#define JSTEEMANN_ATOI_H 1 + +#pragma once + +#include +#include + +// some macros to help with branch prediction +#if defined(__GNUC__) || defined(__GNUG__) +#define ATOI_LIKELY(v) __builtin_expect(!!(v), 1) +#define ATOI_UNLIKELY(v) __builtin_expect(!!(v), 0) +#else +#define ATOI_LIKELY(v) v +#define ATOI_UNLIKELY(v) v +#endif + +#define JSTEEMANN_NOEXCEPT noexcept + +namespace jsteemann { + +enum ErrCode : int { + SUCCESS, + INVALID_INPUT = -1, + INVALID_NEGATIVE_SIGN = -2, + VALUE_OVERFLOW = -3, + VALUE_UNDERFLOW= -4 +}; +// +// errcode +// 0 : success +// -1 : invalid input +// -2 : negative sign(`-`) detected for positive atoi +// -3 : overflow +// -4 : underflow +// + +// low-level worker function to convert the string value between p +// (inclusive) and e (exclusive) into a negative number value of type T, +// without validation of the input string - use this only for trusted input! +// +// the input string will always be interpreted as a base-10 number. +// expects the input string to contain only the digits '0' to '9'. +// there is no validation of the input string, and overflow or underflow +// of the result value will not be detected. +// this function will not modify errno. +template +inline T atoi_negative_unchecked(char const* p, char const* e) JSTEEMANN_NOEXCEPT { + T result = 0; + while (p != e) { + result = (result << 1) + (result << 3) - (*(p++) - '0'); + } + return result; +} + +// low-level worker function to convert the string value between p +// (inclusive) and e (exclusive) into a positive number value of type T, +// without validation of the input string - use this only for trusted input! +// +// the input string will always be interpreted as a base-10 number. +// expects the input string to contain only the digits '0' to '9'. +// there is no validation of the input string, and overflow or underflow +// of the result value will not be detected. +// this function will not modify errno. +template +inline T atoi_positive_unchecked(char const* p, char const* e) JSTEEMANN_NOEXCEPT { + T result = 0; + while (p != e) { + result = (result << 1) + (result << 3) + *(p++) - '0'; + } + + return result; +} + +// function to convert the string value between p +// (inclusive) and e (exclusive) into a number value of type T, without +// validation of the input string - use this only for trusted input! +// +// the input string will always be interpreted as a base-10 number. +// expects the input string to contain only the digits '0' to '9'. an +// optional '+' or '-' sign is allowed too. +// there is no validation of the input string, and overflow or underflow +// of the result value will not be detected. +// this function will not modify errno. +template +inline T atoi_unchecked(char const* p, char const* e) JSTEEMANN_NOEXCEPT { + if (ATOI_UNLIKELY(p == e)) { + return T(); + } + + if (*p == '-') { + if (!std::is_signed::value) { + return T(); + } + return atoi_negative_unchecked(++p, e); + } + if (ATOI_UNLIKELY(*p == '+')) { + ++p; + } + + return atoi_positive_unchecked(p, e); +} + +// low-level worker function to convert the string value between p +// (inclusive) and e (exclusive) into a negative number value of type T +// +// the input string will always be interpreted as a base-10 number. +// expects the input string to contain only the digits '0' to '9'. +// if any other character is found, the output parameter "errcode" will +// be set to -1(invalid input). if the parsed value is less than what type T can +// store without truncation, "errcode" will be set to -4(underflow). +// this function will not modify errno. +template +inline T atoi_negative(char const* p, char const* e, int& errcode) JSTEEMANN_NOEXCEPT { + if (ATOI_UNLIKELY(p == e)) { + errcode = -1; + return T(); + } + + constexpr T cutoff = (std::numeric_limits::min)() / 10; + constexpr char cutlim = -((std::numeric_limits::min)() % 10); + T result = 0; + + do { + char c = *p; + + if ((c == '\0') || (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r') || (c == '\v') || (c == '\f')) { + errcode = 0; + return result; + } + + // we expect only '0' to '9'. everything else is unexpected + if (ATOI_UNLIKELY(c < '0' || c > '9')) { + errcode = -1; + return result; + } + + c -= '0'; + // we expect the bulk of values to not hit the bounds restrictions + if (ATOI_UNLIKELY(result < cutoff || (result == cutoff && c > cutlim))) { + errcode = -4; + return result; + } + result *= 10; + result -= c; + } while (++p < e); + + errcode = 0; + return result; +} + +// low-level worker function to convert the string value between p +// (inclusive) and e (exclusive) into a positive number value of type T +// +// the input string will always be interpreted as a base-10 number. +// expects the input string to contain only the digits '0' to '9'. +// if any other character is found, the output parameter "errcode" will +// be set to -1. if the parsed value is greater than what type T can +// store without truncation, "errcode" will be set to -3(overflow). +// this function will not modify errno. +template +inline T atoi_positive(char const* p, char const* e, int& errcode) JSTEEMANN_NOEXCEPT { + if (ATOI_UNLIKELY(p == e)) { + errcode = -1; + return T(); + } + + constexpr T cutoff = (std::numeric_limits::max)() / 10; + constexpr char cutlim = (std::numeric_limits::max)() % 10; + T result = 0; + + do { + char c = *p; + + if ((c == '\0') || (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r') || (c == '\v') || (c == '\f')) { + errcode = 0; + return result; + } + + // we expect only '0' to '9'. everything else is unexpected + if (ATOI_UNLIKELY(c < '0' || c > '9')) { + errcode = -1; + return result; + } + + c -= '0'; + // we expect the bulk of values to not hit the bounds restrictions + if (ATOI_UNLIKELY(result > cutoff || (result == cutoff && c > cutlim))) { + errcode = -3; + return result; + } + result *= 10; + result += c; + } while (++p < e); + + errcode = 0; + return result; +} + +// function to convert the string value between p +// (inclusive) and e (exclusive) into a number value of type T +// +// the input string will always be interpreted as a base-10 number. +// expects the input string to contain only the digits '0' to '9'. an +// optional '+' or '-' sign is allowed too. +// if any other character is found, the output parameter "errcode" will +// be set to -1. if the parsed value is less or greater than what +// type T can store without truncation, "errcode" will be set to +// -3(oveerflow) +// this function will not modify errno. +template +inline typename std::enable_if::value, T>::type atoi(char const* p, char const* e, int& errcode) JSTEEMANN_NOEXCEPT { + if (ATOI_UNLIKELY(p == e)) { + errcode = -1; + return T(); + } + + if (*p == '-') { + return atoi_negative(++p, e, errcode); + } + if (ATOI_UNLIKELY(*p == '+')) { + ++p; + } + + return atoi_positive(p, e, errcode); +} + +template +inline typename std::enable_if::value, T>::type atoi(char const* p, char const* e, int &errcode) JSTEEMANN_NOEXCEPT { + if (ATOI_UNLIKELY(p == e)) { + errcode = -1; + return T(); + } + + if (*p == '-') { + errcode = -2; + return T(); + } + if (ATOI_UNLIKELY(*p == '+')) { + ++p; + } + + return atoi_positive(p, e, errcode); +} + +} + +#endif diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/jsteemann/modification.md b/contrib/tinyusdz/tinyusdz_repo/src/external/jsteemann/modification.md new file mode 100644 index 000000000..435cc09e4 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/jsteemann/modification.md @@ -0,0 +1,3 @@ +Modification by Syoyo Fujita. + +reports overflow error. diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/linalg.README b/contrib/tinyusdz/tinyusdz_repo/src/external/linalg.README new file mode 100644 index 000000000..88c0bd80f --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/linalg.README @@ -0,0 +1,482 @@ +# linalg.h + +[![Release is 2.2-beta](https://img.shields.io/badge/version-2.2--beta-blue.svg)](http://raw.githubusercontent.com/sgorsten/linalg/v3/linalg.h) +[![License is Unlicense](http://img.shields.io/badge/license-Unlicense-blue.svg?style=flat)](http://unlicense.org/) +[![Travis CI build status](http://travis-ci.org/sgorsten/linalg.svg)](https://travis-ci.org/sgorsten/linalg) +[![Appveyor build status](http://ci.appveyor.com/api/projects/status/l4bfv5omodkajuc9?svg=true)](https://ci.appveyor.com/project/sgorsten/linalg) + +[`linalg.h`](/linalg.h) is a [single header](http://github.com/nothings/stb/blob/master/docs/other_libs.md), [public domain](http://unlicense.org/), [short vector math](http://www.reedbeta.com/blog/on-vector-math-libraries/) library for [C++](http://en.cppreference.com/w/). It is inspired by the syntax of popular shading and compute languages and is intended to serve as a lightweight alternative to projects such as [GLM](http://glm.g-truc.net/0.9.7/), [Boost.QVM](https://www.boost.org/doc/libs/1_66_0/libs/qvm/doc/index.html) or [Eigen](http://eigen.tuxfamily.org/) in domains such as computer graphics, computational geometry, and physical simulation. It allows you to easily write programs like the following: + +```cpp +#include +using namespace linalg::aliases; + +// Compute the coefficients of the equation of a plane containing points a, b, and c +float4 compute_plane(float3 a, float3 b, float3 c) +{ + float3 n = cross(b-a, c-a); + return {n, -dot(n,a)}; +} +``` + +`linalg.h` aims to be: + +* **Lightweight**: The library is defined in a single header file which is less than a thousand lines of code. +* **Dependency free**: There are no dependencies beyond a compliant C++11 compiler and a small subset of the standard library. +* **Standards compliant**: Almost all operations are free of undefined behavior and can be evaluated in a `constexpr` context. +* **Generic**: All types and operations are parameterized over scalar type, and can be mixed within expressions. Type promotion rules roughly match the C standard. +* **Consistent**: Named functions and overloaded operators perform the same conceptual operation on all data types for which they are supported. +* **Complete**: There are very few restrictions on which operations may be applied to which data types. +* **Easy to integrate**: The library defines no symbols in the public namespace, and provides a mechanism for defining implicit conversions to external or user-provided data types. + +The documentation for `v2.2` is still in progress. + +* [Data structures](#data-structures) + * [Vectors](#vectors) + * [Matrices](#matrices) +* [Function listing](#function-listing) + * [Vector algebra](#vector-algebra) + * [Quaternion algebra](#quaternion-algebra) + * [Matrix algebra](#matrix-algebra) + * [Component-wise operations](#component-wise-operations) + * [Reductions](#reductions) +* [Optional features](#optional-features) + * [Type aliases](#type-aliases) + * [`ostream` overloads](#ostream-overloads) + * [User-defined conversions](#user-defined-conversions) +* [Higher order functions](#higher-order-functions) +* [Changes from v2.1](#changes-from-v21) + +## Data structures + +#### Vectors + +`linalg::vec` defines a fixed-length vector containing exactly `M` elements of type `T`. Convenience aliases such as `float3`, `float4`, or `int2` are provided in the [`linalg::aliases` namespace](#type-aliases). This data structure can be used to store a wide variety of types of data, including geometric vectors, points, homogeneous coordinates, plane equations, colors, texture coordinates, or any other situation where you need to manipulate a small sequence of numbers. As such, `vec` is supported by a set of [algebraic](#vector-algebra) and [component-wise](#component-wise-operations) functions, as well as a set of standard [reductions](#reductions). + +`vec`: +* is [`DefaultConstructible`](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible): + ```cpp + float3 v; // v contains 0,0,0 + ``` +* is constructible from `M` elements of type `T`: + ```cpp + float3 v {1,2,3}; // v contains 1,2,3 + ``` +* is [`CopyConstructible`](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) and [`CopyAssignable`](https://en.cppreference.com/w/cpp/named_req/CopyAssignable): + ```cpp + float3 v {1,2,3}; // v contains 1,2,3 + float3 u {v}; // u contains 1,2,3 + float3 w; // w contains 0,0,0 + w = u; // w contains 1,2,3 + ``` +* is [`EqualityComparable`](https://en.cppreference.com/w/cpp/named_req/EqualityComparable) and [`LessThanComparable`](https://en.cppreference.com/w/cpp/named_req/LessThanComparable): + ```cpp + if(v == y) cout << "v and u contain equal elements in the same positions" << endl; + if(v < u) cout << "v precedes u lexicographically" << endl; + ``` +* is **explicitly** constructible from a single element of type `T`: + ```cpp + float3 v = float3{4}; // v contains 4,4,4 + ``` +* is **explicitly** constructible from a `vec` of some other type `U`: + ```cpp + float3 v {1.1f,2.3f,3.5f}; // v contains 1.1,2.3,3.5 + int3 u = int3{v}; // u contains 1,2,3 + ``` +* has fields `x,y,z,w`: + ```cpp + float y = point.y; // y contains second element of point + pixel.w = 0.5; // fourth element of pixel set to 0.5 + float s = tc.x; // s contains first element of tc + ``` +* supports indexing: + ```cpp + float x = v[0]; // x contains first element of v + v[2] = 5; // third element of v set to 5 + ``` +* supports unary operators `+`, `-`, `!` and `~` in component-wise fashion: + ```cpp + auto v = -float{2,3}; // v is float2{-2,-3} + ``` +* supports binary operators `+`, `-`, `*`, `/`, `%`, `|`, `&`, `^`, `<<` and `>>` in component-wise fashion: + ```cpp + auto v = float2{1,1} + float2{2,3}; // v is float2{3,4} + ``` +* supports binary operators with a scalar on the left or the right: + ```cpp + auto v = 2 * float3{1,2,3}; // v is float3{2,4,6} + auto u = float3{1,2,3} + 1; // u is float3{2,3,4} + ``` +* supports operators `+=`, `-=`, `*=`, `/=`, `%=`, `|=`, `&=`, `^=`, `<<=` and `>>=` with vectors or scalars on the right: + ```cpp + float2 v {1,2}; v *= 3; // v is float2{3,6} + ``` +* supports operations on mixed element types: + ```cpp + auto v = float3{1,2,3} + int3{4,5,6}; // v is float3{5,7,9} + ``` +* supports [range-based for](https://en.cppreference.com/w/cpp/language/range-for): + ```cpp + for(auto elem : float3{1,2,3}) cout << elem << ' '; // prints "1 2 3 " + ``` +* has a flat memory layout: + ```cpp + float3 v {1,2,3}; + float * p = v.data(); // &v[i] == p+i + p[1] = 4; // v contains 1,4,3 + ``` + +#### Matrices + +`linalg::mat` defines a fixed-size matrix containing exactly `M` rows and `N` columns of type `T`, in column-major order. Convenience aliases such as `float4x4` or `double3x3` are provided in the [`linalg::aliases` namespace](#type-aliases). This data structure is supported by a set of [algebraic](#matrix-algebra) functions and [component-wise](#component-wise-operations) functions, as well as a set of standard [reductions](#reductions). + +`mat`: +* is [`DefaultConstructible`](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible): + ```cpp + float2x2 m; // m contains columns 0,0; 0,0 + ``` +* is constructible from `N` columns of type `vec`: + ```cpp + float2x2 m {{1,2},{3,4}}; // m contains columns 1,2; 3,4 + ``` +* is constructible from `linalg::identity`: + ```cpp + float3x3 m = linalg::identity; // m contains columns 1,0,0; 0,1,0; 0,0,1 + ``` +* is [`CopyConstructible`](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) and [`CopyAssignable`](https://en.cppreference.com/w/cpp/named_req/CopyAssignable): + ```cpp + float2x2 m {{1,2},{3,4}}; // m contains columns 1,2; 3,4 + float2x2 n {m}; // n contains columns 1,2; 3,4 + float2x2 p; // p contains columns 0,0; 0,0 + p = n; // p contains columns 1,2; 3,4 + ``` +* is [`EqualityComparable`](https://en.cppreference.com/w/cpp/named_req/EqualityComparable) and [`LessThanComparable`](https://en.cppreference.com/w/cpp/named_req/LessThanComparable): + ```cpp + if(m == n) cout << "m and n contain equal elements in the same positions" << endl; + if(m < n) cout << "m precedes n lexicographically when compared in column-major order" << endl; + ``` +* is **explicitly** constructible from a single element of type `T`: + ```cpp + float2x2 m {5}; // m contains columns 5,5; 5,5 + ``` +* is **explicitly** constructible from a `mat` of some other type `U`: + ```cpp + float2x2 m {int2x2{{5,6},{7,8}}}; // m contains columns 5,6; 7,8 + ``` +* supports indexing into *columns*: + ```cpp + float2x3 m {{1,2},{3,4},{5,6}}; // m contains columns 1,2; 3,4; 5,6 + float2 c = m[0]; // c contains 1,2 + m[1] = {7,8}; // m contains columns 1,2; 7,8; 5,6 + ``` +* supports retrieval (but not assignment) of rows: + ```cpp + float2x3 m {{1,2},{3,4},{5,6}}; // m contains columns 1,2; 3,4; 5,6 + float3 r = m.row(1); // r contains 2,4,6 + ``` + + + +* supports unary operators `+`, `-`, `!` and `~` in component-wise fashion: + ```cpp + float2x2 m {{1,2},{3,4}}; // m contains columns 1,2; 3,4 + float2x2 n = -m; // n contains columns -1,-2; -3,-4 + ``` +* supports binary operators `+`, `-`, `*`, `/`, `%`, `|`, `&`, `^`, `<<` and `>>` in component-wise fashion: + ```cpp + float2x2 a {{0,0},{2,2}}; // a contains columns 0,0; 2,2 + float2x2 b {{1,2},{1,2}}; // b contains columns 1,2; 1,2 + float2x2 c = a + b; // c contains columns 1,2; 3,4 + ``` + +* supports binary operators with a scalar on the left or the right: + ```cpp + auto m = 2 * float2x2{{1,2},{3,4}}; // m is float2x2{{2,4},{6,8}} + ``` + +* supports operators `+=`, `-=`, `*=`, `/=`, `%=`, `|=`, `&=`, `^=`, `<<=` and `>>=` with matrices or scalars on the right: + ```cpp + float2x2 v {{5,4},{3,2}}; + v *= 3; // v is float2x2{{15,12},{9,6}} + ``` + +* supports operations on mixed element types: + +* supports [range-based for](https://en.cppreference.com/w/cpp/language/range-for) over columns + +* has a flat memory layout + +## Function listing + +#### Vector algebra + +* `cross(vec a, vec b) -> vec` is the [cross or vector product](https://en.wikipedia.org/wiki/Cross_product) of vectors `a` and `b` + * `cross(vec a, vec b) -> T` is shorthand for `cross({a.x,a.y,0}, {b.x,b.y,0}).z` + * `cross(T a, vec b) -> vec` is shorthand for `cross({0,0,a.z}, {b.x,b.y,0}).xy()` + * `cross(vec a, T b) -> vec` is shorthand for `cross({a.x,a.y,0}, {0,0,b.z}).xy()` + +* `dot(vec a, vec b) -> T` is the [dot or inner product](https://en.wikipedia.org/wiki/Dot_product) of vectors `a` and `b` + +* `length(vec a) -> T` is the length or magnitude of a vector `a` +* `length2(vec a) -> T` is the *square* of the length or magnitude of vector `a` +* `normalize(vec a) -> vec` is a unit length vector in the same direction as `a` (undefined for zero-length vectors) + +* `distance(vec a, vec b) -> T` is the [Euclidean distance](https://en.wikipedia.org/wiki/Euclidean_distance) between points `a` and `b` +* `distance2(vec a, vec b) -> T` is the *square* of the [Euclidean distance](https://en.wikipedia.org/wiki/Euclidean_distance) between points `a` and `b` + +* `angle(vec a, vec b) -> T` is the angle in [radians](https://en.wikipedia.org/wiki/Radian) between vectors `a` and `b` +* `uangle(vec a, vec b) -> T` is the angle in [radians](https://en.wikipedia.org/wiki/Radian) between unit vectors `a` and `b` (undefined for non-unit vectors) +* `rot(T a, vec v) -> vec` is the vector `v` rotated counter-clockwise by the angle `a` in [radians](https://en.wikipedia.org/wiki/Radian) + +* `nlerp(vec a, vec b, T t) -> vec` is shorthand for `normalize(lerp(a,b,t))` +* `slerp(vec a, vec b, T t) -> vec` is the [spherical linear interpolation](https://en.wikipedia.org/wiki/Slerp) between unit vectors `a` and `b` (undefined for non-unit vectors) by parameter `t` + +#### Quaternion algebra + +A small set of functions provides support for quaternion math, using `vec` values to represent quaternions of the form `xi + yj + zk + w`. + +* `qmul(vec a, vec b) -> vec` is the [Hamilton product](https://en.wikipedia.org/wiki/Quaternion#Hamilton_product) of quaternions `a` and `b` +* `qconj(vec q) -> vec` is the [conjugate](https://en.wikipedia.org/wiki/Quaternion#Conjugation,_the_norm,_and_reciprocal) of quaternion `q` +* `qinv(vec q) -> vec` is the [inverse or reciprocal](https://en.wikipedia.org/wiki/Quaternion#Conjugation,_the_norm,_and_reciprocal) of quaternion `q` (undefined for zero-length quaternions) + +* `qexp(vec q) -> vec` is the [exponential](https://en.wikipedia.org/wiki/Quaternion#Exponential,_logarithm,_and_power_functions) of quaternion `q` +* `qlog(vec q) -> vec` is the [logarithm](https://en.wikipedia.org/wiki/Quaternion#Exponential,_logarithm,_and_power_functions) of quaternion `q` +* `qpow(vec q T p) -> vec` is the quaternion `q` raised to the exponent `p` + +A second set of functions provides support for using unit-length quaternions to represent 3D spatial rotations. Their results are undefined for quaternions which are not of unit-length. + +* `qangle(vec q)` is the angle in radians of the rotation expressed by quaternion `q` +* `qaxis(vec q)` is the axis of rotation expression by quaternion `q` (undefined for zero-angle quaternions) +* `qrot(vec q, vec v) -> vec` is vector `v` rotated via rotation quaternion `q` + +* `qmat(vec q)` is a 3x3 rotation matrix which performs the same operation as rotation quaternion `q` +* `qxdir(vec q)` is (efficient) shorthand for `qrot(q, {1,0,0})` +* `qydir(vec q)` is (efficient) shorthand for `qrot(q, {0,1,0})` +* `qzdir(vec q)` is (efficient) shorthand for `qrot(q, {0,0,1})` + +It is possible to use the `nlerp` and `slerp` functions to interpolate rotation quaternions as though they were simply four-dimensional vectors. However, the rotation quaternions form a [double cover](https://en.wikipedia.org/wiki/Covering_group) over spatial rotations in three dimensions. This means that there are **two** distinct rotation quaternions representing each spatial rotation. Naively interpolating between two spatial rotations using quaternions could follow the "short path" or the "long path" between these rotations, depending on which specific quaternions are being interpolated. + +* `qnlerp(vec a, vec b, T t)` is similar to `nlerp(a,b,t)`, but always chooses the "short path" between the rotations represented by `a` and `b`. +* `qslerp(vec a, vec b, T t)` is similar to `slerp(a,b,t)`, but always chooses the "short path" between the rotations represented by `a` and `b`. + +#### Matrix algebra + +* `mul(mat a, mat b) -> mat` is the [matrix product](https://en.wikipedia.org/wiki/Matrix_multiplication) of matrices `a` and `b` +** `mul(mat a, vec b) -> vec` is the [matrix product](https://en.wikipedia.org/wiki/Matrix_multiplication) of matrix `a` and a column matrix containing the elements of vector `b` +** `mul(a, b, c)` is shorthand for `mul(mul(a, b), c)` + +* `outerprod(vec a, vec b) -> mat` is the [outer product](https://en.wikipedia.org/wiki/Outer_product) of vectors `a` and `b` + +* `diagonal(mat a) -> vec` is a vector containing the elements along the main diagonal of matrix `a` +* `trace(mat a) -> T` is the sum of the elements along the main diagonal of matrix `a` + +* `transpose(mat a) -> mat` is the [transpose](https://en.wikipedia.org/wiki/Transpose) of matrix `a` +* `adjugate(mat a) -> mat` is the [adjugate or classical adjoint](https://en.wikipedia.org/wiki/Adjugate_matrix) of matrix `a` (the transpose of its cofactor matrix, or the numerator in the expression of its inverse) +* `comatrix(mat a) -> mat` is the [comatrix or cofactor matrix](https://en.wikipedia.org/wiki/Minor_(linear_algebra)#Inverse_of_a_matrix) of matrix `a` (the transpose of its adjugate matrix) + +* `determinant(mat a) -> T` is the [determinant](https://en.wikipedia.org/wiki/Determinant) of matrix `a` +* `inverse(mat a) -> mat` is the [multiplicative inverse](https://en.wikipedia.org/wiki/Multiplicative_inverse) of the [invertible matrix](https://en.wikipedia.org/wiki/Invertible_matrix) `a` (undefined for singular inputs) + +#### Component-wise operations + +The unary functions `abs`, `floor`, `ceil`, `exp`, `log`, `log10`, `sqrt`, `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `sinh`, `cosh`, `tanh`, `round` accept a vector-valued argument and produce a vector-valued result by passing individual elements to the function of the same name in the `std::` namespace, as defined by `` or ``. + +```cpp +float4 a {1,-4,9,-16}; // a contains 1,-4,9,-16 +float4 b = abs(a); // b contains 1,4,9,16 +float4 c = sqrt(b); // c contains 1,2,3,4 +``` + +The binary functions `fmod`, `pow`, `atan2`, and `copysign` function similarly, except that either argument can be a vector or a scalar. + +```cpp +float2 a {5,4}, b {2,3}; +float2 c = pow(a, 2); // c contains 25,16 +float2 d = pow(2, b); // d contains 4,8 +float2 e = pow(a, b); // e contains 25,64 +``` + +The binary functions `equal`, `nequal`, `less`, `greater`, `lequal`, and `gequal` apply operators `==`, `!=`, `<`, `>`, `<=` and `>=` respectively in a component-wise fashion, returning a `vec`. As before, either argument can be a vector or a scalar. + +```cpp +int2 a {2,5}, b {3,4}; +bool2 c = less(a,3); // c contains true, false +bool2 d = equal(4,b); // d contains false, true +bool2 e = greater(a,b); // e contains false, true +``` + +`min(a,b) -> vec` performs the component-wise selection of lesser elements, as by `a[i] < b[i] ? a[i] : b[i]`. Either argument can be a vector or a scalar. + +`max(a,b) -> vec` performs the component-wise selection of greater elements, as by `a[i] > b[i] ? a[i] : b[i]`. Either argument can be a vector or a scalar. + +`clamp(x,l,h) -> vec` performs the component-wise clamping of elements between a low and high boundary, as by `min(max(x,l),h)`. Any argument can be a vector or a scalar. + +`select(p,a,b) -> vec` performs a component-wise ternary operator, as by `p[i] ? a[i] : b[i]`. Any argument can be a vector or a scalar. + +`lerp(a,b,t) -> vec` performs a component-wise linear interpolation, as by `a[i]*(1-t[i]) + b[i]*t[i]`. Any argument can be a vector or a scalar. + +#### Reductions + +* `any(vec a) -> bool` is `true` if any element of the vector `a` is `true` +* `all(vec a) -> bool` is `true` if all elements of the vector `a` are `true` +* `sum(vec a) -> T` is the sum of all elements in the vector `a` +* `product(vec a) -> T` returns the product of all elements in the vector `a` +* `minelem(vec a) -> T` returns the **value** of the least element in the vector `a` +* `maxelem(vec a) -> T` returns the **value** of the greatest element in the vector `a` +* `argmin(vec a) -> int` returns the **zero-based index** of the least element in the vector `a` +* `argmax(vec a) -> int` returns the **zero-based index** of the greatest element in the vector `a` + +#### Comparisons + +`compare(a,b)` is conceptually equivalent to `operator <=>` from [C++20](https://en.cppreference.com/w/cpp/language/default_comparisons). It compares two values of equivalent shape and returns a value which supports all six standard comparisons against `0`. It provides the same ordering guarantees as the underlying scalar type. That is, a `vec` provides a strong ordering, where a `vec` provides a partial odering. + +## Optional features + +#### Type aliases + +By default, `linalg.h` does not define any symbols in the global namespace, and a three-element vector of single-precision floating point values must be spelled `linalg::vec`. In various libraries and shading languages, such a type might be spelled `float3`, `vec3`, `vec3f`, `point3f`, `simd_float3`, or any one of a hundred other possibilities. `linalg.h` provides a collection of useful aliases in the `linalg::aliases` namespace. If the names specified in this namespace are suitable for a user's purposes, they can quickly be brought into scope as follows: + +```cpp +#include +using namespace linalg::aliases; + +float3 a_vector; +float4x4 a_matrix; +``` + +Note that this **only** brings the type aliases into global scope. The core types and all functions and operator overloads defined by the library remain in `namespace linalg`. + +If the spellings in `namespace linalg::aliases` conflict with other types that have been defined in the global namespace or in other namespaces of interest, the user can choose to omit the `using namespace` directive and instead define their own aliases as desired. + +```cpp +#include +using v3f = linalg::vec; +using m44f = linalg::mat; + +v3f a_vector; +m44f a_matrix; +``` + +It is, of course, always possible to use the core `linalg.h` types directly if operating in an environment where no additional symbols should be defined. + +```cpp +#include + +linalg::vec a_vector; +linalg::mat a_matrix; +``` + +The set of type aliases defined in `namespace linalg::aliases` is as follows: + +* `vec` aliased to *floatM*, as in: `float1`, `float2`, `float3`, `float4` +* `vec` aliased to *doubleM*, as in: `double1`, `double2`, `double3`, `double4` +* `vec` aliased to *intM* as in: `int1`, `int2`, `int3`, `int4` +* `vec` aliased to *uintM* as in: `uint1`, `uint2`, `uint3`, `uint4` +* `vec` aliased to *boolM* as in: `bool1`, `bool2`, `bool3`, `bool4` +* `vec` aliased to *shortM* as in: `short1`, `short2`, `short3`, `short4` +* `vec` aliased to *ushortM* as in: `ushort1`, `ushort2`, `ushort3`, `ushort4` +* `vec` aliased to *byteM* as in: `byte1`, `byte2`, `byte3`, `byte4` +* `mat` aliased to *floatMxN* as in: `float1x3`, `float3x2`, `float4x4`, etc. +* `mat` aliased to *doubleMxN* as in: `double1x3`, `double3x2`, `double4x4`, etc. +* `mat` aliased to *intMxN* as in: `int1x3`, `int3x2`, `int4x4`, etc. +* `mat` aliased to *boolMxN* as in: `boolx3`, `bool3x2`, `bool4x4`, etc. + +All combinations of up to four elements, rows, or columns are provided. + +#### `ostream` overloads + +By default, `linalg.h` does not provide operators for interaction with standard library streams. This is to permit maximum flexibility for users who wish to define their own formatting (with or without delimiters, row versus column major matrices, human-readable precision or round-trip exact). However, as it is often useful to simply be able to show something when writing small programs, we provide some default stream operator overloads which can be brought into scope with: + +```cpp +#include "linalg.h" +using namespace linalg::ostream_overloads; +``` + +The provided behavior is to output a string using the currently specified stream properties (width, precision, padding, etc) which matches the braced-initialization syntax that could be used to construct that same value, without any extra whitespace. + +```cpp +int3 v {1, 2, 3}; +int2x2 m {{4, 5}, {6, 7}}; +std::cout << v << std::endl; // Prints {1,2,3} +std::wcout << m << std::endl; // Prints {{4,5},{6,7}} +``` + +#### User-defined conversions + +A mechanism exists to define automatic conversions between `linalg` and user-provided types. As an example, this mechanism has already been used to defined bidirectional conversions between `linalg::vec` and `std::array`. + +**TODO: Explain `converter`** + +## Higher order functions + +#### `linalg::fold(f, a, b)` + +`fold(f, a, b)` is a higher order function which accepts a function of the form `A,B => A` and repeatedly invokes `a = f(a, element_of_b)` until all elements have been consumed, before returning `a`. It is approximately equal to a [left fold with an initial value](https://en.wikipedia.org/wiki/Fold_(higher-order_function)). When `b` is a `vec`, elements are folded from least to greatest index. When `b` is a `mat`, elements are folded in column-major order. + +See also: [Reductions](#reductions) + +#### `linalg::apply(f, a...)` + +`apply(f, a...)` is a higher order function which accepts a function of the form `A... => T` and applies it to component-wise sets of elements from data structures of compatible shape and dimensions. It is approximately equal to a [convolution](https://en.wikipedia.org/wiki/Convolution_(computer_science)) followed by a [map](https://en.wikipedia.org/wiki/Map_(higher-order_function)). The shape of the result (that is, whether it is a scalar, vector, or matrix, and the dimensions thereof) is determined by the arguments. If more than one argument is a non-scalar, the shape of those arguments must agree. Scalars can be freely intermixed with non-scalars, and element types can also be freely mixed. The element type of the returned value is determined by the return type of the provided mapping function `f`. The supported call signatures are enumerated in the following table: + +| call | type of `a` | type of `b` | type of `c` | result type | result elements | +|------------------|--------------|--------------|-------------|--------------|--------------------------| +| `apply(f,a)` | `A` | | | `T` | `f(a)` | +| `apply(f,a)` | `vec` | | | `vec` | `f(a[i])...` | +| `apply(f,a)` | `mat` | | | `mat` | `f(a[j][i])...` | +| `apply(f,a,b)` | `A` | `B` | | `T` | `f(a, b)...` | +| `apply(f,a,b)` | `A` | `vec` | | `vec` | `f(a, b[i])...` | +| `apply(f,a,b)` | `vec` | `B` | | `vec` | `f(a[i], b)...` | +| `apply(f,a,b)` | `vec` | `vec` | | `vec` | `f(a[i], b[i])...` | +| `apply(f,a,b)` | `A` | `mat` | | `mat` | `f(a, b[j][i])...` | +| `apply(f,a,b)` | `mat` | `B` | | `mat` | `f(a[j][i], b)...` | +| `apply(f,a,b)` | `mat` | `mat` | | `mat` | `f(a[j][i], b[j][i])...` | +| `apply(f,a,b,c)` | `A` | `B` | `C` | `T` | `f(a, b, c)...` | +| `apply(f,a,b,c)` | `A` | `B` | `vec` | `vec` | `f(a, b, c[i])...` | +| `apply(f,a,b,c)` | `A` | `vec` | `C` | `vec` | `f(a, b[i], c)...` | +| `apply(f,a,b,c)` | `A` | `vec` | `vec` | `vec` | `f(a, b[i], c[i])...` | +| `apply(f,a,b,c)` | `vec` | `B` | `C` | `vec` | `f(a[i], b, c)...` | +| `apply(f,a,b,c)` | `vec` | `B` | `vec` | `vec` | `f(a[i], b, c[i])...` | +| `apply(f,a,b,c)` | `vec` | `vec` | `C` | `vec` | `f(a[i], b[i], c)...` | +| `apply(f,a,b,c)` | `vec` | `vec` | `vec` | `vec` | `f(a[i], b[i], c[i])...` | + +**TODO: Explain `apply_t` and SFINAE helpers.** + +See also: [Component-wise operations](#component-wise-operations) + +## Changes from `v2.1` + +#### Improvements in `v2.2` + +* `map(a,f)` and `zip(a,b,f)` subsumed by new `apply(f,a...)` + * `apply(...)` supports unary, binary, and ternary operations for `vec` + * `apply(...)` supports unary and binary operations for `mat` and `quat` + * `apply(...)` can also be invoked exclusively with scalars, and supports arbitrary numbers of arguments + * `apply(...)` supports mixed element types + * Template type alias `apply_t` provides the return type of `apply(f,a...)` +* `vec` and `mat` specializations are now provided +* `compare(a,b)` provide three-way comparison between compatible types +* `clamp(a,b,c)` can be invoked with three distinct (but compatible) types +* `select(a,b,c)` provides the a component-wise equivalent to `a ? b : c` +* `lerp(a,b,t)` has been generalized to a component-wise operation where any of `a`, `b`, and `t` can be vectors or scalars +* User can specialize `converter` to enable implicit conversions from `U` to `T`, if either type is a `vec`, `mat`, or `quat` + * `identity` is implemented using this facility to serve as an in-library example +* No undefined behavior according to the C++11 standard +* Almost all operations which do not internally call `` functions are `constexpr`, except for `argmin` and `argmax` +* No lambdas are used in `linalg.h`, avoiding potential ODR violations + +#### Deprecations in `v2.2` + +* `operator *` has been deprecated between pairs of matrices. + * Call `cmul(...)` if the original, component-wise product was intended + * Call `mul(...)` if the algebraic matrix product was intended + +You can `#define LINALG_FORWARD_COMPATIBLE` before including `linalg.h` to remove all deprecated features. + +#### Breaking changes in `v2.2-beta` + +It is intended that compatibility will be restored before officially tagging `v2.2` + +* `linalg.h` no longer supports Visual Studio 2013. However, it is known to work on GCC 4.9+, Clang 3.5+ in C++11 mode and Visual Studio 2015+. +* `vec` and `mat` may only be used with a `T` which is an [arithmetic type](https://en.cppreference.com/w/c/language/arithmetic_types) + * This requirement will likely be relaxed, but will require specializing some trait type to indicate additional scalar types diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/linalg.UNLICENSE b/contrib/tinyusdz/tinyusdz_repo/src/external/linalg.UNLICENSE new file mode 100644 index 000000000..00d2e135a --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/linalg.UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to \ No newline at end of file diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/linalg.h b/contrib/tinyusdz/tinyusdz_repo/src/external/linalg.h new file mode 100644 index 000000000..aff292ac2 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/linalg.h @@ -0,0 +1,721 @@ +// linalg.h - 2.2-beta - Single-header public domain linear algebra library +// +// The intent of this library is to provide the bulk of the functionality +// you need to write programs that frequently use small, fixed-size vectors +// and matrices, in domains such as computational geometry or computer +// graphics. It strives for terse, readable source code. +// +// The original author of this software is Sterling Orsten, and its permanent +// home is . If you find this software +// useful, an acknowledgement in your source text and/or product documentation +// is appreciated, but not required. +// +// The author acknowledges significant insights and contributions by: +// Stan Melax +// Dimitri Diakopoulos +// +// Some features are deprecated. Define LINALG_FORWARD_COMPATIBLE to remove them. + + + +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or +// distribute this software, either in source code form or as a compiled +// binary, for any purpose, commercial or non-commercial, and by any +// means. +// +// In jurisdictions that recognize copyright laws, the author or authors +// of this software dedicate any and all copyright interest in the +// software to the public domain. We make this dedication for the benefit +// of the public at large and to the detriment of our heirs and +// successors. We intend this dedication to be an overt act of +// relinquishment in perpetuity of all present and future rights to this +// software under copyright law. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// For more information, please refer to + + + +#pragma once +#ifndef LINALG_H +#define LINALG_H + +#include // For various unary math functions, such as std::sqrt +#include // To resolve std::abs ambiguity on clang +#include // For implementing namespace linalg::aliases +#include // For std::array +#include // For forward definitions of std::ostream +#include // For std::enable_if, std::is_same, std::declval +#include // For std::hash declaration + +// In Visual Studio 2015, `constexpr` applied to a member function implies `const`, which causes ambiguous overload resolution +#if _MSC_VER <= 1900 +#define LINALG_CONSTEXPR14 +#else +#define LINALG_CONSTEXPR14 constexpr +#endif + +namespace linalg +{ + // Small, fixed-length vector type, consisting of exactly M elements of type T, and presumed to be a column-vector unless otherwise noted. + template struct vec; + + // Small, fixed-size matrix type, consisting of exactly M rows and N columns of type T, stored in column-major order. + template struct mat; + + // Specialize converter with a function application operator that converts type U to type T to enable implicit conversions + template struct converter {}; + namespace detail + { + template using conv_t = typename std::enable_if::value, decltype(converter{}(std::declval()))>::type; + + // Trait for retrieving scalar type of any linear algebra object + template struct scalar_type {}; + template struct scalar_type> { using type = T; }; + template struct scalar_type> { using type = T; }; + + // Type returned by the compare(...) function which supports all six comparison operators against 0 + template struct ord { T a,b; }; + template constexpr bool operator == (const ord & o, std::nullptr_t) { return o.a == o.b; } + template constexpr bool operator != (const ord & o, std::nullptr_t) { return !(o.a == o.b); } + template constexpr bool operator < (const ord & o, std::nullptr_t) { return o.a < o.b; } + template constexpr bool operator > (const ord & o, std::nullptr_t) { return o.b < o.a; } + template constexpr bool operator <= (const ord & o, std::nullptr_t) { return !(o.b < o.a); } + template constexpr bool operator >= (const ord & o, std::nullptr_t) { return !(o.a < o.b); } + + // Patterns which can be used with the compare(...) function + template struct any_compare {}; + template struct any_compare,vec> { using type=ord; constexpr ord operator() (const vec & a, const vec & b) const { return ord{a.x,b.x}; } }; + template struct any_compare,vec> { using type=ord; constexpr ord operator() (const vec & a, const vec & b) const { return !(a.x==b.x) ? ord{a.x,b.x} : ord{a.y,b.y}; } }; + template struct any_compare,vec> { using type=ord; constexpr ord operator() (const vec & a, const vec & b) const { return !(a.x==b.x) ? ord{a.x,b.x} : !(a.y==b.y) ? ord{a.y,b.y} : ord{a.z,b.z}; } }; + template struct any_compare,vec> { using type=ord; constexpr ord operator() (const vec & a, const vec & b) const { return !(a.x==b.x) ? ord{a.x,b.x} : !(a.y==b.y) ? ord{a.y,b.y} : !(a.z==b.z) ? ord{a.z,b.z} : ord{a.w,b.w}; } }; + template struct any_compare,mat> { using type=ord; constexpr ord operator() (const mat & a, const mat & b) const { return compare(a.x,b.x); } }; + template struct any_compare,mat> { using type=ord; constexpr ord operator() (const mat & a, const mat & b) const { return a.x!=b.x ? compare(a.x,b.x) : compare(a.y,b.y); } }; + template struct any_compare,mat> { using type=ord; constexpr ord operator() (const mat & a, const mat & b) const { return a.x!=b.x ? compare(a.x,b.x) : a.y!=b.y ? compare(a.y,b.y) : compare(a.z,b.z); } }; + template struct any_compare,mat> { using type=ord; constexpr ord operator() (const mat & a, const mat & b) const { return a.x!=b.x ? compare(a.x,b.x) : a.y!=b.y ? compare(a.y,b.y) : a.z!=b.z ? compare(a.z,b.z) : compare(a.w,b.w); } }; + + // Helper for compile-time index-based access to members of vector and matrix types + template struct getter; + template<> struct getter<0> { template constexpr auto operator() (A & a) const -> decltype(a.x) { return a.x; } }; + template<> struct getter<1> { template constexpr auto operator() (A & a) const -> decltype(a.y) { return a.y; } }; + template<> struct getter<2> { template constexpr auto operator() (A & a) const -> decltype(a.z) { return a.z; } }; + template<> struct getter<3> { template constexpr auto operator() (A & a) const -> decltype(a.w) { return a.w; } }; + + // Stand-in for std::integer_sequence/std::make_integer_sequence + template struct seq {}; + template struct make_seq_impl; + template struct make_seq_impl { using type=seq<>; }; + template struct make_seq_impl { using type=seq; }; + template struct make_seq_impl { using type=seq; }; + template struct make_seq_impl { using type=seq; }; + template struct make_seq_impl { using type=seq; }; + template using make_seq = typename make_seq_impl::type; + template vec constexpr swizzle(const vec & v, seq i) { return {getter{}(v)...}; } + template mat constexpr swizzle(const mat & m, seq i, seq j) { return {swizzle(getter{}(m),i)...}; } + + // SFINAE helpers to determine result of function application + template using ret_t = decltype(std::declval()(std::declval()...)); + + // SFINAE helper which is defined if all provided types are scalars + struct empty {}; + template struct scalars; + template<> struct scalars<> { using type=void; }; + template struct scalars : std::conditional::value, scalars, empty>::type {}; + template using scalars_t = typename scalars::type; + + // Helpers which indicate how apply(F, ...) should be called for various arguments + template struct apply {}; // Patterns which contain only vectors or scalars + template struct apply, vec > { using type=vec,M>; enum {size=M, mm=0}; template static constexpr type impl(seq, F f, const vec & a ) { return {f(getter{}(a) )...}; } }; + template struct apply, vec, vec > { using type=vec,M>; enum {size=M, mm=0}; template static constexpr type impl(seq, F f, const vec & a, const vec & b ) { return {f(getter{}(a), getter{}(b) )...}; } }; + template struct apply, vec, B > { using type=vec,M>; enum {size=M, mm=0}; template static constexpr type impl(seq, F f, const vec & a, B b ) { return {f(getter{}(a), b )...}; } }; + template struct apply, A, vec > { using type=vec,M>; enum {size=M, mm=0}; template static constexpr type impl(seq, F f, A a, const vec & b ) { return {f(a, getter{}(b) )...}; } }; + template struct apply, vec, vec, vec> { using type=vec,M>; enum {size=M, mm=0}; template static constexpr type impl(seq, F f, const vec & a, const vec & b, const vec & c) { return {f(getter{}(a), getter{}(b), getter{}(c))...}; } }; + template struct apply, vec, vec, C > { using type=vec,M>; enum {size=M, mm=0}; template static constexpr type impl(seq, F f, const vec & a, const vec & b, C c) { return {f(getter{}(a), getter{}(b), c )...}; } }; + template struct apply, vec, B, vec> { using type=vec,M>; enum {size=M, mm=0}; template static constexpr type impl(seq, F f, const vec & a, B b, const vec & c) { return {f(getter{}(a), b, getter{}(c))...}; } }; + template struct apply, vec, B, C > { using type=vec,M>; enum {size=M, mm=0}; template static constexpr type impl(seq, F f, const vec & a, B b, C c) { return {f(getter{}(a), b, c )...}; } }; + template struct apply, A, vec, vec> { using type=vec,M>; enum {size=M, mm=0}; template static constexpr type impl(seq, F f, A a, const vec & b, const vec & c) { return {f(a, getter{}(b), getter{}(c))...}; } }; + template struct apply, A, vec, C > { using type=vec,M>; enum {size=M, mm=0}; template static constexpr type impl(seq, F f, A a, const vec & b, C c) { return {f(a, getter{}(b), c )...}; } }; + template struct apply, A, B, vec> { using type=vec,M>; enum {size=M, mm=0}; template static constexpr type impl(seq, F f, A a, B b, const vec & c) { return {f(a, b, getter{}(c))...}; } }; + template struct apply, mat > { using type=mat,M,N>; enum {size=N, mm=0}; template static constexpr type impl(seq, F f, const mat & a ) { return {apply >::impl(make_seq<0,M>{}, f, getter{}(a) )...}; } }; + template struct apply, mat, mat> { using type=mat,M,N>; enum {size=N, mm=1}; template static constexpr type impl(seq, F f, const mat & a, const mat & b) { return {apply, vec>::impl(make_seq<0,M>{}, f, getter{}(a), getter{}(b))...}; } }; + template struct apply, mat, B > { using type=mat,M,N>; enum {size=N, mm=0}; template static constexpr type impl(seq, F f, const mat & a, B b) { return {apply, B >::impl(make_seq<0,M>{}, f, getter{}(a), b )...}; } }; + template struct apply, A, mat> { using type=mat,M,N>; enum {size=N, mm=0}; template static constexpr type impl(seq, F f, A a, const mat & b) { return {apply>::impl(make_seq<0,M>{}, f, a, getter{}(b))...}; } }; + template struct apply, A...> { using type = ret_t; enum {size=0, mm=0}; static constexpr type impl(seq<>, F f, A... a) { return f(a...); } }; + + // Function objects for selecting between alternatives + struct min { template constexpr auto operator() (A a, B b) const -> typename std::remove_reference::type { return a constexpr auto operator() (A a, B b) const -> typename std::remove_reference::type { return a constexpr auto operator() (A a, B b, C c) const -> typename std::remove_reference::type { return a constexpr auto operator() (A a, B b, C c) const -> typename std::remove_reference::type { return a ? b : c; } }; + struct lerp { template constexpr auto operator() (A a, B b, C c) const -> decltype(a*(1-c) + b*c) { return a*(1-c) + b*c; } }; + + // Function objects for applying operators + struct op_pos { template constexpr auto operator() (A a) const -> decltype(+a) { return +a; } }; + struct op_neg { template constexpr auto operator() (A a) const -> decltype(-a) { return -a; } }; + struct op_not { template constexpr auto operator() (A a) const -> decltype(!a) { return !a; } }; + struct op_cmp { template constexpr auto operator() (A a) const -> decltype(~(a)) { return ~a; } }; + struct op_mul { template constexpr auto operator() (A a, B b) const -> decltype(a * b) { return a * b; } }; + struct op_div { template constexpr auto operator() (A a, B b) const -> decltype(a / b) { return a / b; } }; + struct op_mod { template constexpr auto operator() (A a, B b) const -> decltype(a % b) { return a % b; } }; + struct op_add { template constexpr auto operator() (A a, B b) const -> decltype(a + b) { return a + b; } }; + struct op_sub { template constexpr auto operator() (A a, B b) const -> decltype(a - b) { return a - b; } }; + struct op_lsh { template constexpr auto operator() (A a, B b) const -> decltype(a << b) { return a << b; } }; + struct op_rsh { template constexpr auto operator() (A a, B b) const -> decltype(a >> b) { return a >> b; } }; + struct op_lt { template constexpr auto operator() (A a, B b) const -> decltype(a < b) { return a < b; } }; + struct op_gt { template constexpr auto operator() (A a, B b) const -> decltype(a > b) { return a > b; } }; + struct op_le { template constexpr auto operator() (A a, B b) const -> decltype(a <= b) { return a <= b; } }; + struct op_ge { template constexpr auto operator() (A a, B b) const -> decltype(a >= b) { return a >= b; } }; + struct op_eq { template constexpr auto operator() (A a, B b) const -> decltype(a == b) { return a == b; } }; + struct op_ne { template constexpr auto operator() (A a, B b) const -> decltype(a != b) { return a != b; } }; + struct op_int { template constexpr auto operator() (A a, B b) const -> decltype(a & b) { return a & b; } }; + struct op_xor { template constexpr auto operator() (A a, B b) const -> decltype(a ^ b) { return a ^ b; } }; + struct op_un { template constexpr auto operator() (A a, B b) const -> decltype(a | b) { return a | b; } }; + struct op_and { template constexpr auto operator() (A a, B b) const -> decltype(a && b) { return a && b; } }; + struct op_or { template constexpr auto operator() (A a, B b) const -> decltype(a || b) { return a || b; } }; + + // Function objects for applying standard library math functions + struct std_abs { template auto operator() (A a) const -> decltype(std::abs (a)) { return std::abs (a); } }; + struct std_floor { template auto operator() (A a) const -> decltype(std::floor(a)) { return std::floor(a); } }; + struct std_ceil { template auto operator() (A a) const -> decltype(std::ceil (a)) { return std::ceil (a); } }; + struct std_exp { template auto operator() (A a) const -> decltype(std::exp (a)) { return std::exp (a); } }; + struct std_log { template auto operator() (A a) const -> decltype(std::log (a)) { return std::log (a); } }; + struct std_log10 { template auto operator() (A a) const -> decltype(std::log10(a)) { return std::log10(a); } }; + struct std_sqrt { template auto operator() (A a) const -> decltype(std::sqrt (a)) { return std::sqrt (a); } }; + struct std_sin { template auto operator() (A a) const -> decltype(std::sin (a)) { return std::sin (a); } }; + struct std_cos { template auto operator() (A a) const -> decltype(std::cos (a)) { return std::cos (a); } }; + struct std_tan { template auto operator() (A a) const -> decltype(std::tan (a)) { return std::tan (a); } }; + struct std_asin { template auto operator() (A a) const -> decltype(std::asin (a)) { return std::asin (a); } }; + struct std_acos { template auto operator() (A a) const -> decltype(std::acos (a)) { return std::acos (a); } }; + struct std_atan { template auto operator() (A a) const -> decltype(std::atan (a)) { return std::atan (a); } }; + struct std_sinh { template auto operator() (A a) const -> decltype(std::sinh (a)) { return std::sinh (a); } }; + struct std_cosh { template auto operator() (A a) const -> decltype(std::cosh (a)) { return std::cosh (a); } }; + struct std_tanh { template auto operator() (A a) const -> decltype(std::tanh (a)) { return std::tanh (a); } }; + struct std_round { template auto operator() (A a) const -> decltype(std::round(a)) { return std::round(a); } }; + struct std_fmod { template auto operator() (A a, B b) const -> decltype(std::fmod (a, b)) { return std::fmod (a, b); } }; + struct std_pow { template auto operator() (A a, B b) const -> decltype(std::pow (a, b)) { return std::pow (a, b); } }; + struct std_atan2 { template auto operator() (A a, B b) const -> decltype(std::atan2 (a, b)) { return std::atan2 (a, b); } }; + struct std_copysign { template auto operator() (A a, B b) const -> decltype(std::copysign(a, b)) { return std::copysign(a, b); } }; + } + + // Small, fixed-length vector type, consisting of exactly M elements of type T, and presumed to be a column-vector unless otherwise noted + template struct vec + { + T x; + constexpr vec() : x() {} + constexpr vec(const T & x_) : x(x_) {} + // NOTE: vec does NOT have a constructor from pointer, this can conflict with initializing its single element from zero + template + constexpr explicit vec(const vec & v) : vec(static_cast(v.x)) {} + constexpr const T & operator[] (int i) const { return x; } + LINALG_CONSTEXPR14 T & operator[] (int i) { return x; } + + template> constexpr vec(const U & u) : vec(converter{}(u)) {} + template> constexpr operator U () const { return converter{}(*this); } + }; + template struct vec + { + T x,y; + constexpr vec() : x(), y() {} + constexpr vec(const T & x_, const T & y_) : x(x_), y(y_) {} + constexpr explicit vec(const T & s) : vec(s, s) {} + constexpr explicit vec(const T * p) : vec(p[0], p[1]) {} + template + constexpr explicit vec(const vec & v) : vec(static_cast(v.x), static_cast(v.y)) {} + constexpr const T & operator[] (int i) const { return i==0?x:y; } + LINALG_CONSTEXPR14 T & operator[] (int i) { return i==0?x:y; } + + template> constexpr vec(const U & u) : vec(converter{}(u)) {} + template> constexpr operator U () const { return converter{}(*this); } + }; + template struct vec + { + T x,y,z; + constexpr vec() : x(), y(), z() {} + constexpr vec(const T & x_, const T & y_, + const T & z_) : x(x_), y(y_), z(z_) {} + constexpr vec(const vec & xy, + const T & z_) : vec(xy.x, xy.y, z_) {} + constexpr explicit vec(const T & s) : vec(s, s, s) {} + constexpr explicit vec(const T * p) : vec(p[0], p[1], p[2]) {} + template + constexpr explicit vec(const vec & v) : vec(static_cast(v.x), static_cast(v.y), static_cast(v.z)) {} + constexpr const T & operator[] (int i) const { return i==0?x:i==1?y:z; } + LINALG_CONSTEXPR14 T & operator[] (int i) { return i==0?x:i==1?y:z; } + constexpr const vec & xy() const { return *reinterpret_cast *>(this); } + vec & xy() { return *reinterpret_cast *>(this); } + + template> constexpr vec(const U & u) : vec(converter{}(u)) {} + template> constexpr operator U () const { return converter{}(*this); } + }; + template struct vec + { + T x,y,z,w; + constexpr vec() : x(), y(), z(), w() {} + constexpr vec(const T & x_, const T & y_, + const T & z_, const T & w_) : x(x_), y(y_), z(z_), w(w_) {} + constexpr vec(const vec & xy, + const T & z_, const T & w_) : vec(xy.x, xy.y, z_, w_) {} + constexpr vec(const vec & xyz, + const T & w_) : vec(xyz.x, xyz.y, xyz.z, w_) {} + constexpr explicit vec(const T & s) : vec(s, s, s, s) {} + constexpr explicit vec(const T * p) : vec(p[0], p[1], p[2], p[3]) {} + template + constexpr explicit vec(const vec & v) : vec(static_cast(v.x), static_cast(v.y), static_cast(v.z), static_cast(v.w)) {} + constexpr const T & operator[] (int i) const { return i==0?x:i==1?y:i==2?z:w; } + LINALG_CONSTEXPR14 T & operator[] (int i) { return i==0?x:i==1?y:i==2?z:w; } + constexpr const vec & xy() const { return *reinterpret_cast *>(this); } + constexpr const vec & xyz() const { return *reinterpret_cast *>(this); } + vec & xy() { return *reinterpret_cast *>(this); } + vec & xyz() { return *reinterpret_cast *>(this); } + + template> constexpr vec(const U & u) : vec(converter{}(u)) {} + template> constexpr operator U () const { return converter{}(*this); } + }; + + // Small, fixed-size matrix type, consisting of exactly M rows and N columns of type T, stored in column-major order. + template struct mat + { + typedef vec V; + V x; + constexpr mat() : x() {} + constexpr mat(const V & x_) : x(x_) {} + constexpr explicit mat(const T & s) : x(s) {} + constexpr explicit mat(const T * p) : x(p+M*0) {} + template + constexpr explicit mat(const mat & m) : mat(V(m.x)) {} + constexpr vec row(int i) const { return {x[i]}; } + constexpr const V & operator[] (int j) const { return x; } + LINALG_CONSTEXPR14 V & operator[] (int j) { return x; } + + template> constexpr mat(const U & u) : mat(converter{}(u)) {} + template> constexpr operator U () const { return converter{}(*this); } + }; + template struct mat + { + typedef vec V; + V x,y; + constexpr mat() : x(), y() {} + constexpr mat(const V & x_, const V & y_) : x(x_), y(y_) {} + constexpr explicit mat(const T & s) : x(s), y(s) {} + constexpr explicit mat(const T * p) : x(p+M*0), y(p+M*1) {} + template + constexpr explicit mat(const mat & m) : mat(V(m.x), V(m.y)) {} + constexpr vec row(int i) const { return {x[i], y[i]}; } + constexpr const V & operator[] (int j) const { return j==0?x:y; } + LINALG_CONSTEXPR14 V & operator[] (int j) { return j==0?x:y; } + + template> constexpr mat(const U & u) : mat(converter{}(u)) {} + template> constexpr operator U () const { return converter{}(*this); } + }; + template struct mat + { + typedef vec V; + V x,y,z; + constexpr mat() : x(), y(), z() {} + constexpr mat(const V & x_, const V & y_, + const V & z_) : x(x_), y(y_), z(z_) {} + constexpr explicit mat(const T & s) : x(s), y(s), z(s) {} + constexpr explicit mat(const T * p) : x(p+M*0), y(p+M*1), z(p+M*2) {} + template + constexpr explicit mat(const mat & m) : mat(V(m.x), V(m.y), V(m.z)) {} + constexpr vec row(int i) const { return {x[i], y[i], z[i]}; } + constexpr const V & operator[] (int j) const { return j==0?x:j==1?y:z; } + LINALG_CONSTEXPR14 V & operator[] (int j) { return j==0?x:j==1?y:z; } + + template> constexpr mat(const U & u) : mat(converter{}(u)) {} + template> constexpr operator U () const { return converter{}(*this); } + }; + template struct mat + { + typedef vec V; + V x,y,z,w; + constexpr mat() : x(), y(), z(), w() {} + constexpr mat(const V & x_, const V & y_, + const V & z_, const V & w_) : x(x_), y(y_), z(z_), w(w_) {} + constexpr explicit mat(const T & s) : x(s), y(s), z(s), w(s) {} + constexpr explicit mat(const T * p) : x(p+M*0), y(p+M*1), z(p+M*2), w(p+M*3) {} + template + constexpr explicit mat(const mat & m) : mat(V(m.x), V(m.y), V(m.z), V(m.w)) {} + constexpr vec row(int i) const { return {x[i], y[i], z[i], w[i]}; } + constexpr const V & operator[] (int j) const { return j==0?x:j==1?y:j==2?z:w; } + LINALG_CONSTEXPR14 V & operator[] (int j) { return j==0?x:j==1?y:j==2?z:w; } + + template> constexpr mat(const U & u) : mat(converter{}(u)) {} + template> constexpr operator U () const { return converter{}(*this); } + }; + + // Define a type which will convert to the multiplicative identity of any square matrix + struct identity_t { constexpr explicit identity_t(int) {} }; + template struct converter, identity_t> { constexpr mat operator() (identity_t) const { return {vec{1}}; } }; + template struct converter, identity_t> { constexpr mat operator() (identity_t) const { return {{1,0},{0,1}}; } }; + template struct converter, identity_t> { constexpr mat operator() (identity_t) const { return {{1,0,0},{0,1,0},{0,0,1}}; } }; + template struct converter, identity_t> { constexpr mat operator() (identity_t) const { return {{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,1}}; } }; + constexpr identity_t identity {1}; + + // Produce a scalar by applying f(A,B) -> A to adjacent pairs of elements from a vec/mat in left-to-right/column-major order (matching the associativity of arithmetic and logical operators) + template constexpr A fold(F f, A a, const vec & b) { return f(a, b.x); } + template constexpr A fold(F f, A a, const vec & b) { return f(f(a, b.x), b.y); } + template constexpr A fold(F f, A a, const vec & b) { return f(f(f(a, b.x), b.y), b.z); } + template constexpr A fold(F f, A a, const vec & b) { return f(f(f(f(a, b.x), b.y), b.z), b.w); } + template constexpr A fold(F f, A a, const mat & b) { return fold(f, a, b.x); } + template constexpr A fold(F f, A a, const mat & b) { return fold(f, fold(f, a, b.x), b.y); } + template constexpr A fold(F f, A a, const mat & b) { return fold(f, fold(f, fold(f, a, b.x), b.y), b.z); } + template constexpr A fold(F f, A a, const mat & b) { return fold(f, fold(f, fold(f, fold(f, a, b.x), b.y), b.z), b.w); } + + // Type aliases for the result of calling apply(...) with various arguments, can be used with return type SFINAE to constrian overload sets + template using apply_t = typename detail::apply::type; + template using mm_apply_t = typename std::enable_if::mm, apply_t>::type; + template using no_mm_apply_t = typename std::enable_if::mm, apply_t>::type; + template using scalar_t = typename detail::scalar_type::type; // Underlying scalar type when performing elementwise operations + + // apply(f,...) applies the provided function in an elementwise fashion to its arguments, producing an object of the same dimensions + template constexpr apply_t apply(F func, const A & ... args) { return detail::apply::impl(detail::make_seq<0,detail::apply::size>{}, func, args...); } + + // map(a,f) is equivalent to apply(f,a) + template constexpr apply_t map(const A & a, F func) { return apply(func, a); } + + // zip(a,b,f) is equivalent to apply(f,a,b) + template constexpr apply_t zip(const A & a, const B & b, F func) { return apply(func, a, b); } + + // Relational operators are defined to compare the elements of two vectors or matrices lexicographically, in column-major order + template constexpr typename detail::any_compare::type compare(const A & a, const B & b) { return detail::any_compare()(a,b); } + template constexpr auto operator == (const A & a, const B & b) -> decltype(compare(a,b) == 0) { return compare(a,b) == 0; } + template constexpr auto operator != (const A & a, const B & b) -> decltype(compare(a,b) != 0) { return compare(a,b) != 0; } + template constexpr auto operator < (const A & a, const B & b) -> decltype(compare(a,b) < 0) { return compare(a,b) < 0; } + template constexpr auto operator > (const A & a, const B & b) -> decltype(compare(a,b) > 0) { return compare(a,b) > 0; } + template constexpr auto operator <= (const A & a, const B & b) -> decltype(compare(a,b) <= 0) { return compare(a,b) <= 0; } + template constexpr auto operator >= (const A & a, const B & b) -> decltype(compare(a,b) >= 0) { return compare(a,b) >= 0; } + + // Functions for coalescing scalar values + template constexpr bool any (const A & a) { return fold(detail::op_or{}, false, a); } + template constexpr bool all (const A & a) { return fold(detail::op_and{}, true, a); } + template constexpr scalar_t sum (const A & a) { return fold(detail::op_add{}, scalar_t(0), a); } + template constexpr scalar_t product(const A & a) { return fold(detail::op_mul{}, scalar_t(1), a); } + template constexpr scalar_t minelem(const A & a) { return fold(detail::min{}, a.x, a); } + template constexpr scalar_t maxelem(const A & a) { return fold(detail::max{}, a.x, a); } + template int argmin(const vec & a) { int j=0; for(int i=1; i int argmax(const vec & a) { int j=0; for(int i=1; i a[j]) j = i; return j; } + + // Unary operators are defined component-wise for linalg types + template constexpr apply_t operator + (const A & a) { return apply(detail::op_pos{}, a); } + template constexpr apply_t operator - (const A & a) { return apply(detail::op_neg{}, a); } + template constexpr apply_t operator ~ (const A & a) { return apply(detail::op_cmp{}, a); } + template constexpr apply_t operator ! (const A & a) { return apply(detail::op_not{}, a); } + + // Binary operators are defined component-wise for linalg types, EXCEPT for `operator *` + template constexpr apply_t operator + (const A & a, const B & b) { return apply(detail::op_add{}, a, b); } + template constexpr apply_t operator - (const A & a, const B & b) { return apply(detail::op_sub{}, a, b); } + template constexpr apply_t cmul (const A & a, const B & b) { return apply(detail::op_mul{}, a, b); } + template constexpr apply_t operator / (const A & a, const B & b) { return apply(detail::op_div{}, a, b); } + template constexpr apply_t operator % (const A & a, const B & b) { return apply(detail::op_mod{}, a, b); } + template constexpr apply_t operator | (const A & a, const B & b) { return apply(detail::op_un{}, a, b); } + template constexpr apply_t operator ^ (const A & a, const B & b) { return apply(detail::op_xor{}, a, b); } + template constexpr apply_t operator & (const A & a, const B & b) { return apply(detail::op_int{}, a, b); } + template constexpr apply_t operator << (const A & a, const B & b) { return apply(detail::op_lsh{}, a, b); } + template constexpr apply_t operator >> (const A & a, const B & b) { return apply(detail::op_rsh{}, a, b); } + + // Binary `operator *` was originally defined component-wise for all patterns, in a fashion consistent with the other operators. However, + // this was one of the most frequent sources of confusion among new users of this library, with the binary `operator *` being used accidentally + // by users who INTENDED the semantics of the algebraic matrix product, but RECEIVED the semantics of the Hadamard product. While there is + // precedent within the HLSL, Fortran, R, APL, J, and Mathematica programming languages for `operator *` having the semantics of the Hadamard + // product between matrices, it is counterintuitive to users of GLSL, Eigen, and many other languages and libraries that chose matrix product + // semantics for `operator *`. + // + // For these reasons, binary `operator *` is now DEPRECATED between pairs of matrices. Users may call `cmul(...)` for component-wise multiplication, + // or `mul(...)` for matrix multiplication. Binary `operator *` continues to be available for vector * vector, vector * scalar, matrix * scalar, etc. + template constexpr no_mm_apply_t operator * (const A & a, const B & b) { return cmul(a,b); } + #ifndef LINALG_FORWARD_COMPATIBLE + template [[deprecated("`operator *` between pairs of matrices is deprecated. See the source text for details.")]] constexpr mm_apply_t operator * (const A & a, const B & b) { return cmul(a,b); } + #endif + + // Binary assignment operators a $= b is always defined as though it were explicitly written a = a $ b + template constexpr auto operator += (A & a, const B & b) -> decltype(a = a + b) { return a = a + b; } + template constexpr auto operator -= (A & a, const B & b) -> decltype(a = a - b) { return a = a - b; } + template constexpr auto operator *= (A & a, const B & b) -> decltype(a = a * b) { return a = a * b; } + template constexpr auto operator /= (A & a, const B & b) -> decltype(a = a / b) { return a = a / b; } + template constexpr auto operator %= (A & a, const B & b) -> decltype(a = a % b) { return a = a % b; } + template constexpr auto operator |= (A & a, const B & b) -> decltype(a = a | b) { return a = a | b; } + template constexpr auto operator ^= (A & a, const B & b) -> decltype(a = a ^ b) { return a = a ^ b; } + template constexpr auto operator &= (A & a, const B & b) -> decltype(a = a & b) { return a = a & b; } + template constexpr auto operator <<= (A & a, const B & b) -> decltype(a = a << b) { return a = a << b; } + template constexpr auto operator >>= (A & a, const B & b) -> decltype(a = a >> b) { return a = a >> b; } + + // Swizzles and subobjects + template constexpr vec swizzle(const vec & a) { return {detail::getter{}(a)...}; } + template constexpr vec subvec (const vec & a) { return detail::swizzle(a, detail::make_seq{}); } + template constexpr mat submat (const mat & a) { return detail::swizzle(a, detail::make_seq{}, detail::make_seq{}); } + + // Component-wise standard library math functions + template apply_t abs (const A & a) { return apply(detail::std_abs{}, a); } + template apply_t floor(const A & a) { return apply(detail::std_floor{}, a); } + template apply_t ceil (const A & a) { return apply(detail::std_ceil{}, a); } + template apply_t exp (const A & a) { return apply(detail::std_exp{}, a); } + template apply_t log (const A & a) { return apply(detail::std_log{}, a); } + template apply_t log10(const A & a) { return apply(detail::std_log10{}, a); } + template apply_t sqrt (const A & a) { return apply(detail::std_sqrt{}, a); } + template apply_t sin (const A & a) { return apply(detail::std_sin{}, a); } + template apply_t cos (const A & a) { return apply(detail::std_cos{}, a); } + template apply_t tan (const A & a) { return apply(detail::std_tan{}, a); } + template apply_t asin (const A & a) { return apply(detail::std_asin{}, a); } + template apply_t acos (const A & a) { return apply(detail::std_acos{}, a); } + template apply_t atan (const A & a) { return apply(detail::std_atan{}, a); } + template apply_t sinh (const A & a) { return apply(detail::std_sinh{}, a); } + template apply_t cosh (const A & a) { return apply(detail::std_cosh{}, a); } + template apply_t tanh (const A & a) { return apply(detail::std_tanh{}, a); } + template apply_t round(const A & a) { return apply(detail::std_round{}, a); } + + template apply_t fmod (const A & a, const B & b) { return apply(detail::std_fmod{}, a, b); } + template apply_t pow (const A & a, const B & b) { return apply(detail::std_pow{}, a, b); } + template apply_t atan2 (const A & a, const B & b) { return apply(detail::std_atan2{}, a, b); } + template apply_t copysign(const A & a, const B & b) { return apply(detail::std_copysign{}, a, b); } + + // Component-wise relational functions on vectors + template constexpr apply_t equal (const A & a, const B & b) { return apply(detail::op_eq{}, a, b); } + template constexpr apply_t nequal (const A & a, const B & b) { return apply(detail::op_ne{}, a, b); } + template constexpr apply_t less (const A & a, const B & b) { return apply(detail::op_lt{}, a, b); } + template constexpr apply_t greater(const A & a, const B & b) { return apply(detail::op_gt{}, a, b); } + template constexpr apply_t lequal (const A & a, const B & b) { return apply(detail::op_le{}, a, b); } + template constexpr apply_t gequal (const A & a, const B & b) { return apply(detail::op_ge{}, a, b); } + + // Component-wise selection functions on vectors + template constexpr apply_t min(const A & a, const B & b) { return apply(detail::min{}, a, b); } + template constexpr apply_t max(const A & a, const B & b) { return apply(detail::max{}, a, b); } + template constexpr apply_t clamp (const X & x, const L & l, const H & h) { return apply(detail::clamp{}, x, l, h); } + template constexpr apply_t select(const P & p, const A & a, const B & b) { return apply(detail::select{}, p, a, b); } + template constexpr apply_t lerp (const A & a, const B & b, const T & t) { return apply(detail::lerp{}, a, b, t); } + + // Support for vector algebra + template constexpr T cross (const vec & a, const vec & b) { return a.x*b.y-a.y*b.x; } + template constexpr vec cross (T a, const vec & b) { return {-a*b.y, a*b.x}; } + template constexpr vec cross (const vec & a, T b) { return {a.y*b, -a.x*b}; } + template constexpr vec cross (const vec & a, const vec & b) { return {a.y*b.z-a.z*b.y, a.z*b.x-a.x*b.z, a.x*b.y-a.y*b.x}; } + template constexpr T dot (const vec & a, const vec & b) { return sum(a*b); } + template constexpr T length2 (const vec & a) { return dot(a,a); } + template T length (const vec & a) { return std::sqrt(length2(a)); } + template vec normalize(const vec & a) { return a / length(a); } + template constexpr T distance2(const vec & a, const vec & b) { return length2(b-a); } + template T distance (const vec & a, const vec & b) { return length(b-a); } + template T uangle (const vec & a, const vec & b) { T d=dot(a,b); return d > 1 ? 0 : std::acos(d < -1 ? -1 : d); } + template T angle (const vec & a, const vec & b) { return uangle(normalize(a), normalize(b)); } + template vec rot (T a, const vec & v) { const T s = std::sin(a), c = std::cos(a); return {v.x*c - v.y*s, v.x*s + v.y*c}; } + template vec nlerp (const vec & a, const vec & b, T t) { return normalize(lerp(a,b,t)); } + template vec slerp (const vec & a, const vec & b, T t) { T th=uangle(a,b); return th == 0 ? a : a*(std::sin(th*(1-t))/std::sin(th)) + b*(std::sin(th*t)/std::sin(th)); } + + // Support for quaternion algebra using 4D vectors, representing xi + yj + zk + w + template constexpr vec qconj(const vec & q) { return {-q.x,-q.y,-q.z,q.w}; } + template vec qinv (const vec & q) { return qconj(q)/length2(q); } + template vec qexp (const vec & q) { const auto v = q.xyz(); const auto vv = length(v); return std::exp(q.w) * vec{v * (vv > 0 ? std::sin(vv)/vv : 0), std::cos(vv)}; } + template vec qlog (const vec & q) { const auto v = q.xyz(); const auto vv = length(v), qq = length(q); return {v * (vv > 0 ? std::acos(q.w/qq)/vv : 0), std::log(qq)}; } + template vec qpow (const vec & q, const T & p) { const auto v = q.xyz(); const auto vv = length(v), qq = length(q), th = std::acos(q.w/qq); return std::pow(qq,p)*vec{v * (vv > 0 ? std::sin(p*th)/vv : 0), std::cos(p*th)}; } + template constexpr vec qmul (const vec & a, const vec & b) { return {a.x*b.w+a.w*b.x+a.y*b.z-a.z*b.y, a.y*b.w+a.w*b.y+a.z*b.x-a.x*b.z, a.z*b.w+a.w*b.z+a.x*b.y-a.y*b.x, a.w*b.w-a.x*b.x-a.y*b.y-a.z*b.z}; } + template constexpr vec qmul(const vec & a, R... r) { return qmul(a, qmul(r...)); } + + // Support for 3D spatial rotations using quaternions, via qmul(qmul(q, v), qconj(q)) + template constexpr vec qxdir (const vec & q) { return {q.w*q.w+q.x*q.x-q.y*q.y-q.z*q.z, (q.x*q.y+q.z*q.w)*2, (q.z*q.x-q.y*q.w)*2}; } + template constexpr vec qydir (const vec & q) { return {(q.x*q.y-q.z*q.w)*2, q.w*q.w-q.x*q.x+q.y*q.y-q.z*q.z, (q.y*q.z+q.x*q.w)*2}; } + template constexpr vec qzdir (const vec & q) { return {(q.z*q.x+q.y*q.w)*2, (q.y*q.z-q.x*q.w)*2, q.w*q.w-q.x*q.x-q.y*q.y+q.z*q.z}; } + template constexpr mat qmat (const vec & q) { return {qxdir(q), qydir(q), qzdir(q)}; } + template constexpr vec qrot (const vec & q, const vec & v) { return qxdir(q)*v.x + qydir(q)*v.y + qzdir(q)*v.z; } + template T qangle(const vec & q) { return std::atan2(length(q.xyz()), q.w)*2; } + template vec qaxis (const vec & q) { return normalize(q.xyz()); } + template vec qnlerp(const vec & a, const vec & b, T t) { return nlerp(a, dot(a,b) < 0 ? -b : b, t); } + template vec qslerp(const vec & a, const vec & b, T t) { return slerp(a, dot(a,b) < 0 ? -b : b, t); } + + // Support for matrix algebra + template constexpr vec mul(const mat & a, const vec & b) { return a.x*b.x; } + template constexpr vec mul(const mat & a, const vec & b) { return a.x*b.x + a.y*b.y; } + template constexpr vec mul(const mat & a, const vec & b) { return a.x*b.x + a.y*b.y + a.z*b.z; } + template constexpr vec mul(const mat & a, const vec & b) { return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w; } + template constexpr mat mul(const mat & a, const mat & b) { return {mul(a,b.x)}; } + template constexpr mat mul(const mat & a, const mat & b) { return {mul(a,b.x), mul(a,b.y)}; } + template constexpr mat mul(const mat & a, const mat & b) { return {mul(a,b.x), mul(a,b.y), mul(a,b.z)}; } + template constexpr mat mul(const mat & a, const mat & b) { return {mul(a,b.x), mul(a,b.y), mul(a,b.z), mul(a,b.w)}; } + template constexpr vec mul(const mat & a, const mat & b, const vec & c) { return mul(mul(a,b),c); } + template constexpr mat mul(const mat & a, const mat & b, const mat & c) { return mul(mul(a,b),c); } + template constexpr vec mul(const mat & a, const mat & b, const mat & c, const vec & d) { return mul(mul(a,b,c),d); } + template constexpr mat mul(const mat & a, const mat & b, const mat & c, const mat & d) { return mul(mul(a,b,c),d); } + template constexpr mat outerprod(const vec & a, const vec & b) { return {a*b.x}; } + template constexpr mat outerprod(const vec & a, const vec & b) { return {a*b.x, a*b.y}; } + template constexpr mat outerprod(const vec & a, const vec & b) { return {a*b.x, a*b.y, a*b.z}; } + template constexpr mat outerprod(const vec & a, const vec & b) { return {a*b.x, a*b.y, a*b.z, a*b.w}; } + template constexpr vec diagonal(const mat & a) { return {a.x.x}; } + template constexpr vec diagonal(const mat & a) { return {a.x.x, a.y.y}; } + template constexpr vec diagonal(const mat & a) { return {a.x.x, a.y.y, a.z.z}; } + template constexpr vec diagonal(const mat & a) { return {a.x.x, a.y.y, a.z.z, a.w.w}; } + template constexpr T trace(const mat & a) { return sum(diagonal(a)); } + template constexpr mat transpose(const mat & m) { return {m.row(0)}; } + template constexpr mat transpose(const mat & m) { return {m.row(0), m.row(1)}; } + template constexpr mat transpose(const mat & m) { return {m.row(0), m.row(1), m.row(2)}; } + template constexpr mat transpose(const mat & m) { return {m.row(0), m.row(1), m.row(2), m.row(3)}; } + template constexpr mat transpose(const vec & m) { return transpose(mat(m)); } + template constexpr mat adjugate(const mat & a) { return {vec{1}}; } + template constexpr mat adjugate(const mat & a) { return {{a.y.y, -a.x.y}, {-a.y.x, a.x.x}}; } + template constexpr mat adjugate(const mat & a); + template constexpr mat adjugate(const mat & a); + template constexpr mat comatrix(const mat & a) { return transpose(adjugate(a)); } + template constexpr T determinant(const mat & a) { return a.x.x; } + template constexpr T determinant(const mat & a) { return a.x.x*a.y.y - a.x.y*a.y.x; } + template constexpr T determinant(const mat & a) { return a.x.x*(a.y.y*a.z.z - a.z.y*a.y.z) + a.x.y*(a.y.z*a.z.x - a.z.z*a.y.x) + a.x.z*(a.y.x*a.z.y - a.z.x*a.y.y); } + template constexpr T determinant(const mat & a); + template constexpr mat inverse(const mat & a) { return adjugate(a)/determinant(a); } + + // Vectors and matrices can be used as ranges + template T * begin( vec & a) { return &a.x; } + template const T * begin(const vec & a) { return &a.x; } + template T * end ( vec & a) { return begin(a) + M; } + template const T * end (const vec & a) { return begin(a) + M; } + template vec * begin( mat & a) { return &a.x; } + template const vec * begin(const mat & a) { return &a.x; } + template vec * end ( mat & a) { return begin(a) + N; } + template const vec * end (const mat & a) { return begin(a) + N; } + + // Factory functions for 3D spatial transformations (will possibly be removed or changed in a future version) + enum fwd_axis { neg_z, pos_z }; // Should projection matrices be generated assuming forward is {0,0,-1} or {0,0,1} + enum z_range { neg_one_to_one, zero_to_one }; // Should projection matrices map z into the range of [-1,1] or [0,1]? + template vec rotation_quat (const vec & axis, T angle) { return {axis*std::sin(angle/2), std::cos(angle/2)}; } + template vec rotation_quat (const mat & m); + template mat translation_matrix(const vec & translation) { return {{1,0,0,0},{0,1,0,0},{0,0,1,0},{translation,1}}; } + template mat rotation_matrix (const vec & rotation) { return {{qxdir(rotation),0}, {qydir(rotation),0}, {qzdir(rotation),0}, {0,0,0,1}}; } + template mat scaling_matrix (const vec & scaling) { return {{scaling.x,0,0,0}, {0,scaling.y,0,0}, {0,0,scaling.z,0}, {0,0,0,1}}; } + template mat pose_matrix (const vec & q, const vec & p) { return {{qxdir(q),0}, {qydir(q),0}, {qzdir(q),0}, {p,1}}; } + template mat lookat_matrix (const vec & eye, const vec & center, const vec & view_y_dir, fwd_axis fwd = neg_z); + template mat frustum_matrix (T x0, T x1, T y0, T y1, T n, T f, fwd_axis a = neg_z, z_range z = neg_one_to_one); + template mat perspective_matrix(T fovy, T aspect, T n, T f, fwd_axis a = neg_z, z_range z = neg_one_to_one) { T y = n*std::tan(fovy / 2), x = y*aspect; return frustum_matrix(-x, x, -y, y, n, f, a, z); } + + // Provide implicit conversion between linalg::vec and std::array + template struct converter, std::array> { vec operator() (const std::array & a) const { return {a[0]}; } }; + template struct converter, std::array> { vec operator() (const std::array & a) const { return {a[0], a[1]}; } }; + template struct converter, std::array> { vec operator() (const std::array & a) const { return {a[0], a[1], a[2]}; } }; + template struct converter, std::array> { vec operator() (const std::array & a) const { return {a[0], a[1], a[2], a[3]}; } }; + + template struct converter, vec> { std::array operator() (const vec & a) const { return {a[0]}; } }; + template struct converter, vec> { std::array operator() (const vec & a) const { return {a[0], a[1]}; } }; + template struct converter, vec> { std::array operator() (const vec & a) const { return {a[0], a[1], a[2]}; } }; + template struct converter, vec> { std::array operator() (const vec & a) const { return {a[0], a[1], a[2], a[3]}; } }; + + // Provide typedefs for common element types and vector/matrix sizes + namespace aliases + { + typedef vec bool1; typedef vec byte1; typedef vec short1; typedef vec ushort1; + typedef vec bool2; typedef vec byte2; typedef vec short2; typedef vec ushort2; + typedef vec bool3; typedef vec byte3; typedef vec short3; typedef vec ushort3; + typedef vec bool4; typedef vec byte4; typedef vec short4; typedef vec ushort4; + typedef vec int1; typedef vec uint1; typedef vec float1; typedef vec double1; + typedef vec int2; typedef vec uint2; typedef vec float2; typedef vec double2; + typedef vec int3; typedef vec uint3; typedef vec float3; typedef vec double3; + typedef vec int4; typedef vec uint4; typedef vec float4; typedef vec double4; + typedef mat bool1x1; typedef mat int1x1; typedef mat float1x1; typedef mat double1x1; + typedef mat bool1x2; typedef mat int1x2; typedef mat float1x2; typedef mat double1x2; + typedef mat bool1x3; typedef mat int1x3; typedef mat float1x3; typedef mat double1x3; + typedef mat bool1x4; typedef mat int1x4; typedef mat float1x4; typedef mat double1x4; + typedef mat bool2x1; typedef mat int2x1; typedef mat float2x1; typedef mat double2x1; + typedef mat bool2x2; typedef mat int2x2; typedef mat float2x2; typedef mat double2x2; + typedef mat bool2x3; typedef mat int2x3; typedef mat float2x3; typedef mat double2x3; + typedef mat bool2x4; typedef mat int2x4; typedef mat float2x4; typedef mat double2x4; + typedef mat bool3x1; typedef mat int3x1; typedef mat float3x1; typedef mat double3x1; + typedef mat bool3x2; typedef mat int3x2; typedef mat float3x2; typedef mat double3x2; + typedef mat bool3x3; typedef mat int3x3; typedef mat float3x3; typedef mat double3x3; + typedef mat bool3x4; typedef mat int3x4; typedef mat float3x4; typedef mat double3x4; + typedef mat bool4x1; typedef mat int4x1; typedef mat float4x1; typedef mat double4x1; + typedef mat bool4x2; typedef mat int4x2; typedef mat float4x2; typedef mat double4x2; + typedef mat bool4x3; typedef mat int4x3; typedef mat float4x3; typedef mat double4x3; + typedef mat bool4x4; typedef mat int4x4; typedef mat float4x4; typedef mat double4x4; + } + + // Provide output streaming operators, writing something that resembles an aggregate literal that could be used to construct the specified value + namespace ostream_overloads + { + template std::basic_ostream & operator << (std::basic_ostream & out, const vec & v) { return out << '{' << v[0] << '}'; } + template std::basic_ostream & operator << (std::basic_ostream & out, const vec & v) { return out << '{' << v[0] << ',' << v[1] << '}'; } + template std::basic_ostream & operator << (std::basic_ostream & out, const vec & v) { return out << '{' << v[0] << ',' << v[1] << ',' << v[2] << '}'; } + template std::basic_ostream & operator << (std::basic_ostream & out, const vec & v) { return out << '{' << v[0] << ',' << v[1] << ',' << v[2] << ',' << v[3] << '}'; } + + template std::basic_ostream & operator << (std::basic_ostream & out, const mat & m) { return out << '{' << m[0] << '}'; } + template std::basic_ostream & operator << (std::basic_ostream & out, const mat & m) { return out << '{' << m[0] << ',' << m[1] << '}'; } + template std::basic_ostream & operator << (std::basic_ostream & out, const mat & m) { return out << '{' << m[0] << ',' << m[1] << ',' << m[2] << '}'; } + template std::basic_ostream & operator << (std::basic_ostream & out, const mat & m) { return out << '{' << m[0] << ',' << m[1] << ',' << m[2] << ',' << m[3] << '}'; } + } +} + +namespace std +{ + // Provide specializations for std::hash<...> with linalg types + template struct hash> { std::size_t operator()(const linalg::vec & v) const { std::hash h; return h(v.x); } }; + template struct hash> { std::size_t operator()(const linalg::vec & v) const { std::hash h; return h(v.x) ^ (h(v.y) << 1); } }; + template struct hash> { std::size_t operator()(const linalg::vec & v) const { std::hash h; return h(v.x) ^ (h(v.y) << 1) ^ (h(v.z) << 2); } }; + template struct hash> { std::size_t operator()(const linalg::vec & v) const { std::hash h; return h(v.x) ^ (h(v.y) << 1) ^ (h(v.z) << 2) ^ (h(v.w) << 3); } }; + + template struct hash> { std::size_t operator()(const linalg::mat & v) const { std::hash> h; return h(v.x); } }; + template struct hash> { std::size_t operator()(const linalg::mat & v) const { std::hash> h; return h(v.x) ^ (h(v.y) << M); } }; + template struct hash> { std::size_t operator()(const linalg::mat & v) const { std::hash> h; return h(v.x) ^ (h(v.y) << M) ^ (h(v.z) << (M*2)); } }; + template struct hash> { std::size_t operator()(const linalg::mat & v) const { std::hash> h; return h(v.x) ^ (h(v.y) << M) ^ (h(v.z) << (M*2)) ^ (h(v.w) << (M*3)); } }; +} + +// Definitions of functions too long to be defined inline +template constexpr linalg::mat linalg::adjugate(const mat & a) +{ + return {{a.y.y*a.z.z - a.z.y*a.y.z, a.z.y*a.x.z - a.x.y*a.z.z, a.x.y*a.y.z - a.y.y*a.x.z}, + {a.y.z*a.z.x - a.z.z*a.y.x, a.z.z*a.x.x - a.x.z*a.z.x, a.x.z*a.y.x - a.y.z*a.x.x}, + {a.y.x*a.z.y - a.z.x*a.y.y, a.z.x*a.x.y - a.x.x*a.z.y, a.x.x*a.y.y - a.y.x*a.x.y}}; +} + +template constexpr linalg::mat linalg::adjugate(const mat & a) +{ + return {{a.y.y*a.z.z*a.w.w + a.w.y*a.y.z*a.z.w + a.z.y*a.w.z*a.y.w - a.y.y*a.w.z*a.z.w - a.z.y*a.y.z*a.w.w - a.w.y*a.z.z*a.y.w, + a.x.y*a.w.z*a.z.w + a.z.y*a.x.z*a.w.w + a.w.y*a.z.z*a.x.w - a.w.y*a.x.z*a.z.w - a.z.y*a.w.z*a.x.w - a.x.y*a.z.z*a.w.w, + a.x.y*a.y.z*a.w.w + a.w.y*a.x.z*a.y.w + a.y.y*a.w.z*a.x.w - a.x.y*a.w.z*a.y.w - a.y.y*a.x.z*a.w.w - a.w.y*a.y.z*a.x.w, + a.x.y*a.z.z*a.y.w + a.y.y*a.x.z*a.z.w + a.z.y*a.y.z*a.x.w - a.x.y*a.y.z*a.z.w - a.z.y*a.x.z*a.y.w - a.y.y*a.z.z*a.x.w}, + {a.y.z*a.w.w*a.z.x + a.z.z*a.y.w*a.w.x + a.w.z*a.z.w*a.y.x - a.y.z*a.z.w*a.w.x - a.w.z*a.y.w*a.z.x - a.z.z*a.w.w*a.y.x, + a.x.z*a.z.w*a.w.x + a.w.z*a.x.w*a.z.x + a.z.z*a.w.w*a.x.x - a.x.z*a.w.w*a.z.x - a.z.z*a.x.w*a.w.x - a.w.z*a.z.w*a.x.x, + a.x.z*a.w.w*a.y.x + a.y.z*a.x.w*a.w.x + a.w.z*a.y.w*a.x.x - a.x.z*a.y.w*a.w.x - a.w.z*a.x.w*a.y.x - a.y.z*a.w.w*a.x.x, + a.x.z*a.y.w*a.z.x + a.z.z*a.x.w*a.y.x + a.y.z*a.z.w*a.x.x - a.x.z*a.z.w*a.y.x - a.y.z*a.x.w*a.z.x - a.z.z*a.y.w*a.x.x}, + {a.y.w*a.z.x*a.w.y + a.w.w*a.y.x*a.z.y + a.z.w*a.w.x*a.y.y - a.y.w*a.w.x*a.z.y - a.z.w*a.y.x*a.w.y - a.w.w*a.z.x*a.y.y, + a.x.w*a.w.x*a.z.y + a.z.w*a.x.x*a.w.y + a.w.w*a.z.x*a.x.y - a.x.w*a.z.x*a.w.y - a.w.w*a.x.x*a.z.y - a.z.w*a.w.x*a.x.y, + a.x.w*a.y.x*a.w.y + a.w.w*a.x.x*a.y.y + a.y.w*a.w.x*a.x.y - a.x.w*a.w.x*a.y.y - a.y.w*a.x.x*a.w.y - a.w.w*a.y.x*a.x.y, + a.x.w*a.z.x*a.y.y + a.y.w*a.x.x*a.z.y + a.z.w*a.y.x*a.x.y - a.x.w*a.y.x*a.z.y - a.z.w*a.x.x*a.y.y - a.y.w*a.z.x*a.x.y}, + {a.y.x*a.w.y*a.z.z + a.z.x*a.y.y*a.w.z + a.w.x*a.z.y*a.y.z - a.y.x*a.z.y*a.w.z - a.w.x*a.y.y*a.z.z - a.z.x*a.w.y*a.y.z, + a.x.x*a.z.y*a.w.z + a.w.x*a.x.y*a.z.z + a.z.x*a.w.y*a.x.z - a.x.x*a.w.y*a.z.z - a.z.x*a.x.y*a.w.z - a.w.x*a.z.y*a.x.z, + a.x.x*a.w.y*a.y.z + a.y.x*a.x.y*a.w.z + a.w.x*a.y.y*a.x.z - a.x.x*a.y.y*a.w.z - a.w.x*a.x.y*a.y.z - a.y.x*a.w.y*a.x.z, + a.x.x*a.y.y*a.z.z + a.z.x*a.x.y*a.y.z + a.y.x*a.z.y*a.x.z - a.x.x*a.z.y*a.y.z - a.y.x*a.x.y*a.z.z - a.z.x*a.y.y*a.x.z}}; +} + +template constexpr T linalg::determinant(const mat & a) +{ + return a.x.x*(a.y.y*a.z.z*a.w.w + a.w.y*a.y.z*a.z.w + a.z.y*a.w.z*a.y.w - a.y.y*a.w.z*a.z.w - a.z.y*a.y.z*a.w.w - a.w.y*a.z.z*a.y.w) + + a.x.y*(a.y.z*a.w.w*a.z.x + a.z.z*a.y.w*a.w.x + a.w.z*a.z.w*a.y.x - a.y.z*a.z.w*a.w.x - a.w.z*a.y.w*a.z.x - a.z.z*a.w.w*a.y.x) + + a.x.z*(a.y.w*a.z.x*a.w.y + a.w.w*a.y.x*a.z.y + a.z.w*a.w.x*a.y.y - a.y.w*a.w.x*a.z.y - a.z.w*a.y.x*a.w.y - a.w.w*a.z.x*a.y.y) + + a.x.w*(a.y.x*a.w.y*a.z.z + a.z.x*a.y.y*a.w.z + a.w.x*a.z.y*a.y.z - a.y.x*a.z.y*a.w.z - a.w.x*a.y.y*a.z.z - a.z.x*a.w.y*a.y.z); +} + +template linalg::vec linalg::rotation_quat(const mat & m) +{ + const vec q {m.x.x-m.y.y-m.z.z, m.y.y-m.x.x-m.z.z, m.z.z-m.x.x-m.y.y, m.x.x+m.y.y+m.z.z}, s[] { + {1, m.x.y + m.y.x, m.z.x + m.x.z, m.y.z - m.z.y}, + {m.x.y + m.y.x, 1, m.y.z + m.z.y, m.z.x - m.x.z}, + {m.x.z + m.z.x, m.y.z + m.z.y, 1, m.x.y - m.y.x}, + {m.y.z - m.z.y, m.z.x - m.x.z, m.x.y - m.y.x, 1}}; + return copysign(normalize(sqrt(max(T(0), T(1)+q))), s[argmax(q)]); +} + +template linalg::mat linalg::lookat_matrix(const vec & eye, const vec & center, const vec & view_y_dir, fwd_axis a) +{ + const vec f = normalize(center - eye), z = a == pos_z ? f : -f, x = normalize(cross(view_y_dir, z)), y = cross(z, x); + return inverse(mat{{x,0},{y,0},{z,0},{eye,1}}); +} + +template linalg::mat linalg::frustum_matrix(T x0, T x1, T y0, T y1, T n, T f, fwd_axis a, z_range z) +{ + const T s = a == pos_z ? T(1) : T(-1), o = z == neg_one_to_one ? n : 0; + return {{2*n/(x1-x0),0,0,0}, {0,2*n/(y1-y0),0,0}, {-s*(x0+x1)/(x1-x0),-s*(y0+y1)/(y1-y0),s*(f+o)/(f-n),s}, {0,0,-(n+o)*f/(f-n),0}}; +} + +#endif diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/earcut/CHANGELOG.md b/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/earcut/CHANGELOG.md new file mode 100644 index 000000000..41d9e6703 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/earcut/CHANGELOG.md @@ -0,0 +1,27 @@ +## Earcut.hpp changelog + +### master + + - Fixed a bunch of rare edge cases that led to bad triangulation (parity with Earcut v2.2.2) + - Removed use of deprecated `std::allocator::construct` + - Fixed a minor z-order hashing bug + - Improved visualization app, better docs + +### v0.12.4 + + - Fixed a crash in Crash in Earcut::findHoleBridge + - Added coverage checks + - Added macOS, MinGW builds + +### v0.12.3 + + - Fixed -Wunused-lambda-capture + +### v0.12.2 + + - Fixed potential division by zero + - Fixed -fsanitize=integer warning + +### v0.12.1 + + - Fixed cast precision warning diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/earcut/LICENSE b/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/earcut/LICENSE new file mode 100644 index 000000000..8bafb5773 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/earcut/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2015, Mapbox + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/earcut/README.md b/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/earcut/README.md new file mode 100644 index 000000000..67f235b7f --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/earcut/README.md @@ -0,0 +1,131 @@ +## Earcut + +A C++ port of [earcut.js](https://github.com/mapbox/earcut), a fast, [header-only](https://github.com/mapbox/earcut.hpp/blob/master/include/mapbox/earcut.hpp) polygon triangulation library. + +[![Travis](https://img.shields.io/travis/com/mapbox/earcut.hpp.svg)](https://travis-ci.com/github/mapbox/earcut.hpp) +[![AppVeyor](https://ci.appveyor.com/api/projects/status/a1ysrqd69mqn7coo/branch/master?svg=true)](https://ci.appveyor.com/project/Mapbox/earcut-hpp-8wm4o/branch/master) +[![Coverage](https://img.shields.io/coveralls/github/mapbox/earcut.hpp.svg)](https://coveralls.io/github/mapbox/earcut.hpp) +[![Coverity Scan](https://img.shields.io/coverity/scan/14000.svg)](https://scan.coverity.com/projects/14000) +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/mapbox/earcut.hpp.svg)](http://isitmaintained.com/project/mapbox/earcut.hpp "Average time to resolve an issue") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/mapbox/earcut.hpp.svg)](http://isitmaintained.com/project/mapbox/earcut.hpp "Percentage of issues still open") +[![Mourner](https://img.shields.io/badge/simply-awesome-brightgreen.svg)](https://github.com/mourner/projects) + +The library implements a modified ear slicing algorithm, optimized by [z-order curve](http://en.wikipedia.org/wiki/Z-order_curve) hashing and extended to handle holes, twisted polygons, degeneracies and self-intersections in a way that doesn't _guarantee_ correctness of triangulation, but attempts to always produce acceptable results for practical data like geographical shapes. + +It's based on ideas from [FIST: Fast Industrial-Strength Triangulation of Polygons](http://www.cosy.sbg.ac.at/~held/projects/triang/triang.html) by Martin Held and [Triangulation by Ear Clipping](http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf) by David Eberly. + +## Usage + +```cpp +#include +``` +```cpp +// The number type to use for tessellation +using Coord = double; + +// The index type. Defaults to uint32_t, but you can also pass uint16_t if you know that your +// data won't have more than 65536 vertices. +using N = uint32_t; + +// Create array +using Point = std::array; +std::vector> polygon; + +// Fill polygon structure with actual data. Any winding order works. +// The first polyline defines the main polygon. +polygon.push_back({{100, 0}, {100, 100}, {0, 100}, {0, 0}}); +// Following polylines define holes. +polygon.push_back({{75, 25}, {75, 75}, {25, 75}, {25, 25}}); + +// Run tessellation +// Returns array of indices that refer to the vertices of the input polygon. +// e.g: the index 6 would refer to {25, 75} in this example. +// Three subsequent indices form a triangle. Output triangles are clockwise. +std::vector indices = mapbox::earcut(polygon); +``` + +Earcut can triangulate a simple, planar polygon of any winding order including holes. It will even return a robust, acceptable solution for non-simple poygons. Earcut works on a 2D plane. If you have three or more dimensions, you can project them onto a 2D surface before triangulation, or use a more suitable library for the task (e.g [CGAL](https://doc.cgal.org/latest/Triangulation_3/index.html)). + + +It is also possible to use your custom point type as input. There are default accessors defined for `std::tuple`, `std::pair`, and `std::array`. For a custom type (like Clipper's `IntPoint` type), do this: + +```cpp +// struct IntPoint { +// int64_t X, Y; +// }; + +namespace mapbox { +namespace util { + +template <> +struct nth<0, IntPoint> { + inline static auto get(const IntPoint &t) { + return t.X; + }; +}; +template <> +struct nth<1, IntPoint> { + inline static auto get(const IntPoint &t) { + return t.Y; + }; +}; + +} // namespace util +} // namespace mapbox +``` + +You can also use a custom container type for your polygon. Similar to std::vector, it has to meet the requirements of [Container](https://en.cppreference.com/w/cpp/named_req/Container), in particular `size()`, `empty()` and `operator[]`. + +

+ example triangulation +

+ +## Additional build instructions +In case you just want to use the earcut triangulation library; copy and include the header file [``](https://github.com/mapbox/earcut.hpp/blob/master/include/mapbox/earcut.hpp) in your project and follow the steps documented in the section [Usage](#usage). + +If you want to build the test, benchmark and visualization programs instead, follow these instructions: + +### Dependencies + +Before you continue, make sure to have the following tools and libraries installed: + * git ([Ubuntu](https://help.ubuntu.com/lts/serverguide/git.html)/[Windows/macOS](http://git-scm.com/downloads)) + * cmake 3.2+ ([Ubuntu](https://launchpad.net/~george-edison55/+archive/ubuntu/cmake-3.x)/[Windows/macOS](https://cmake.org/download/)) + * OpenGL SDK ([Ubuntu](http://packages.ubuntu.com/de/trusty/libgl1-mesa-dev)/[Windows](https://dev.windows.com/en-us/downloads/windows-10-sdk)/[macOS](https://developer.apple.com/opengl/)) + * Compiler such as [GCC 4.9+, Clang 3.4+](https://launchpad.net/~ubuntu-toolchain-r/+archive/ubuntu/test), [MSVC12+](https://www.visualstudio.com/) + +Note: On some operating systems such as Windows, manual steps are required to add cmake and [git](http://blog.countableset.ch/2012/06/07/adding-git-to-windows-7-path/) to your PATH environment variable. + +### Manual compilation + +```bash +git clone --recursive https://github.com/mapbox/earcut.hpp.git +cd earcut.hpp +mkdir build +cd build +cmake .. +make +# ./tests +# ./bench +# ./viz +``` + +### [Visual Studio](https://www.visualstudio.com/), [Eclipse](https://eclipse.org/), [XCode](https://developer.apple.com/xcode/), ... + +```batch +git clone --recursive https://github.com/mapbox/earcut.hpp.git +cd earcut.hpp +mkdir project +cd project +cmake .. -G "Visual Studio 14 2015" +::you can also generate projects for "Visual Studio 12 2013", "XCode", "Eclipse CDT4 - Unix Makefiles" +``` +After completion, open the generated project with your IDE. + + +### [CLion](https://www.jetbrains.com/clion/), [Visual Studio 2017+](https://www.visualstudio.com/) + +Import the project from https://github.com/mapbox/earcut.hpp.git and you should be good to go! + +## Status + +This is currently based on [earcut 2.2.4](https://github.com/mapbox/earcut#224-jul-5-2022). diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/earcut/earcut.hpp b/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/earcut/earcut.hpp new file mode 100644 index 000000000..a73ae87c8 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/earcut/earcut.hpp @@ -0,0 +1,816 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mapbox { + +namespace util { + +template struct nth { + inline static typename std::tuple_element::type + get(const T& t) { return std::get(t); }; +}; + +} + +namespace detail { + +template +class Earcut { +public: + std::vector indices; + std::size_t vertices = 0; + + template + void operator()(const Polygon& points); + +private: + struct Node { + Node(N index, double x_, double y_) : i(index), x(x_), y(y_) {} + Node(const Node&) = delete; + Node& operator=(const Node&) = delete; + Node(Node&&) = delete; + Node& operator=(Node&&) = delete; + + const N i; + const double x; + const double y; + + // previous and next vertice nodes in a polygon ring + Node* prev = nullptr; + Node* next = nullptr; + + // z-order curve value + int32_t z = 0; + + // previous and next nodes in z-order + Node* prevZ = nullptr; + Node* nextZ = nullptr; + + // indicates whether this is a steiner point + bool steiner = false; + }; + + template Node* linkedList(const Ring& points, const bool clockwise); + Node* filterPoints(Node* start, Node* end = nullptr); + void earcutLinked(Node* ear, int pass = 0); + bool isEar(Node* ear); + bool isEarHashed(Node* ear); + Node* cureLocalIntersections(Node* start); + void splitEarcut(Node* start); + template Node* eliminateHoles(const Polygon& points, Node* outerNode); + Node* eliminateHole(Node* hole, Node* outerNode); + Node* findHoleBridge(Node* hole, Node* outerNode); + bool sectorContainsSector(const Node* m, const Node* p); + void indexCurve(Node* start); + Node* sortLinked(Node* list); + int32_t zOrder(const double x_, const double y_); + Node* getLeftmost(Node* start); + bool pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const; + bool isValidDiagonal(Node* a, Node* b); + double area(const Node* p, const Node* q, const Node* r) const; + bool equals(const Node* p1, const Node* p2); + bool intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2); + bool onSegment(const Node* p, const Node* q, const Node* r); + int sign(double val); + bool intersectsPolygon(const Node* a, const Node* b); + bool locallyInside(const Node* a, const Node* b); + bool middleInside(const Node* a, const Node* b); + Node* splitPolygon(Node* a, Node* b); + template Node* insertNode(std::size_t i, const Point& p, Node* last); + void removeNode(Node* p); + + bool hashing; + double minX, maxX; + double minY, maxY; + double inv_size = 0; + + template > + class ObjectPool { + public: + ObjectPool() { } + ObjectPool(std::size_t blockSize_) { + reset(blockSize_); + } + ~ObjectPool() { + clear(); + } + template + T* construct(Args&&... args) { + if (currentIndex >= blockSize) { + currentBlock = alloc_traits::allocate(alloc, blockSize); + allocations.emplace_back(currentBlock); + currentIndex = 0; + } + T* object = ¤tBlock[currentIndex++]; + alloc_traits::construct(alloc, object, std::forward(args)...); + return object; + } + void reset(std::size_t newBlockSize) { + for (auto allocation : allocations) { + alloc_traits::deallocate(alloc, allocation, blockSize); + } + allocations.clear(); + blockSize = std::max(1, newBlockSize); + currentBlock = nullptr; + currentIndex = blockSize; + } + void clear() { reset(blockSize); } + private: + T* currentBlock = nullptr; + std::size_t currentIndex = 1; + std::size_t blockSize = 1; + std::vector allocations; + Alloc alloc; + typedef typename std::allocator_traits alloc_traits; + }; + ObjectPool nodes; +}; + +template template +void Earcut::operator()(const Polygon& points) { + // reset + indices.clear(); + vertices = 0; + + if (points.empty()) return; + + double x; + double y; + int threshold = 80; + std::size_t len = 0; + + for (size_t i = 0; threshold >= 0 && i < points.size(); i++) { + threshold -= static_cast(points[i].size()); + len += points[i].size(); + } + + //estimate size of nodes and indices + nodes.reset(len * 3 / 2); + indices.reserve(len + points[0].size()); + + Node* outerNode = linkedList(points[0], true); + if (!outerNode || outerNode->prev == outerNode->next) return; + + if (points.size() > 1) outerNode = eliminateHoles(points, outerNode); + + // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox + hashing = threshold < 0; + if (hashing) { + Node* p = outerNode->next; + minX = maxX = outerNode->x; + minY = maxY = outerNode->y; + do { + x = p->x; + y = p->y; + minX = std::min(minX, x); + minY = std::min(minY, y); + maxX = std::max(maxX, x); + maxY = std::max(maxY, y); + p = p->next; + } while (p != outerNode); + + // minX, minY and inv_size are later used to transform coords into integers for z-order calculation + inv_size = std::max(maxX - minX, maxY - minY); + inv_size = inv_size != .0 ? (32767. / inv_size) : .0; + } + + earcutLinked(outerNode); + + nodes.clear(); +} + +// create a circular doubly linked list from polygon points in the specified winding order +template template +typename Earcut::Node* +Earcut::linkedList(const Ring& points, const bool clockwise) { + using Point = typename Ring::value_type; + double sum = 0; + const std::size_t len = points.size(); + std::size_t i, j; + Node* last = nullptr; + + // calculate original winding order of a polygon ring + for (i = 0, j = len > 0 ? len - 1 : 0; i < len; j = i++) { + const auto& p1 = points[i]; + const auto& p2 = points[j]; + const double p20 = util::nth<0, Point>::get(p2); + const double p10 = util::nth<0, Point>::get(p1); + const double p11 = util::nth<1, Point>::get(p1); + const double p21 = util::nth<1, Point>::get(p2); + sum += (p20 - p10) * (p11 + p21); + } + + // link points into circular doubly-linked list in the specified winding order + if (clockwise == (sum > 0)) { + for (i = 0; i < len; i++) last = insertNode(vertices + i, points[i], last); + } else { + for (i = len; i-- > 0;) last = insertNode(vertices + i, points[i], last); + } + + if (last && equals(last, last->next)) { + removeNode(last); + last = last->next; + } + + vertices += len; + + return last; +} + +// eliminate colinear or duplicate points +template +typename Earcut::Node* +Earcut::filterPoints(Node* start, Node* end) { + if (!end) end = start; + + Node* p = start; + bool again; + do { + again = false; + + if (!p->steiner && (equals(p, p->next) || area(p->prev, p, p->next) == 0)) { + removeNode(p); + p = end = p->prev; + + if (p == p->next) break; + again = true; + + } else { + p = p->next; + } + } while (again || p != end); + + return end; +} + +// main ear slicing loop which triangulates a polygon (given as a linked list) +template +void Earcut::earcutLinked(Node* ear, int pass) { + if (!ear) return; + + // interlink polygon nodes in z-order + if (!pass && hashing) indexCurve(ear); + + Node* stop = ear; + Node* prev; + Node* next; + + int iterations = 0; + + // iterate through ears, slicing them one by one + while (ear->prev != ear->next) { + iterations++; + prev = ear->prev; + next = ear->next; + + if (hashing ? isEarHashed(ear) : isEar(ear)) { + // cut off the triangle + indices.emplace_back(prev->i); + indices.emplace_back(ear->i); + indices.emplace_back(next->i); + + removeNode(ear); + + // skipping the next vertice leads to less sliver triangles + ear = next->next; + stop = next->next; + + continue; + } + + ear = next; + + // if we looped through the whole remaining polygon and can't find any more ears + if (ear == stop) { + // try filtering points and slicing again + if (!pass) earcutLinked(filterPoints(ear), 1); + + // if this didn't work, try curing all small self-intersections locally + else if (pass == 1) { + ear = cureLocalIntersections(filterPoints(ear)); + earcutLinked(ear, 2); + + // as a last resort, try splitting the remaining polygon into two + } else if (pass == 2) splitEarcut(ear); + + break; + } + } +} + +// check whether a polygon node forms a valid ear with adjacent nodes +template +bool Earcut::isEar(Node* ear) { + const Node* a = ear->prev; + const Node* b = ear; + const Node* c = ear->next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // now make sure we don't have other points inside the potential ear + Node* p = ear->next->next; + + while (p != ear->prev) { + if (pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && + area(p->prev, p, p->next) >= 0) return false; + p = p->next; + } + + return true; +} + +template +bool Earcut::isEarHashed(Node* ear) { + const Node* a = ear->prev; + const Node* b = ear; + const Node* c = ear->next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // triangle bbox; min & max are calculated like this for speed + const double minTX = std::min(a->x, std::min(b->x, c->x)); + const double minTY = std::min(a->y, std::min(b->y, c->y)); + const double maxTX = std::max(a->x, std::max(b->x, c->x)); + const double maxTY = std::max(a->y, std::max(b->y, c->y)); + + // z-order range for the current triangle bbox; + const int32_t minZ = zOrder(minTX, minTY); + const int32_t maxZ = zOrder(maxTX, maxTY); + + // first look for points inside the triangle in increasing z-order + Node* p = ear->nextZ; + + while (p && p->z <= maxZ) { + if (p != ear->prev && p != ear->next && + pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && + area(p->prev, p, p->next) >= 0) return false; + p = p->nextZ; + } + + // then look for points in decreasing z-order + p = ear->prevZ; + + while (p && p->z >= minZ) { + if (p != ear->prev && p != ear->next && + pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && + area(p->prev, p, p->next) >= 0) return false; + p = p->prevZ; + } + + return true; +} + +// go through all polygon nodes and cure small local self-intersections +template +typename Earcut::Node* +Earcut::cureLocalIntersections(Node* start) { + Node* p = start; + do { + Node* a = p->prev; + Node* b = p->next->next; + + // a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2]) + if (!equals(a, b) && intersects(a, p, p->next, b) && locallyInside(a, b) && locallyInside(b, a)) { + indices.emplace_back(a->i); + indices.emplace_back(p->i); + indices.emplace_back(b->i); + + // remove two nodes involved + removeNode(p); + removeNode(p->next); + + p = start = b; + } + p = p->next; + } while (p != start); + + return filterPoints(p); +} + +// try splitting polygon into two and triangulate them independently +template +void Earcut::splitEarcut(Node* start) { + // look for a valid diagonal that divides the polygon into two + Node* a = start; + do { + Node* b = a->next->next; + while (b != a->prev) { + if (a->i != b->i && isValidDiagonal(a, b)) { + // split the polygon in two by the diagonal + Node* c = splitPolygon(a, b); + + // filter colinear points around the cuts + a = filterPoints(a, a->next); + c = filterPoints(c, c->next); + + // run earcut on each half + earcutLinked(a); + earcutLinked(c); + return; + } + b = b->next; + } + a = a->next; + } while (a != start); +} + +// link every hole into the outer loop, producing a single-ring polygon without holes +template template +typename Earcut::Node* +Earcut::eliminateHoles(const Polygon& points, Node* outerNode) { + const size_t len = points.size(); + + std::vector queue; + for (size_t i = 1; i < len; i++) { + Node* list = linkedList(points[i], false); + if (list) { + if (list == list->next) list->steiner = true; + queue.push_back(getLeftmost(list)); + } + } + std::sort(queue.begin(), queue.end(), [](const Node* a, const Node* b) { + return a->x < b->x; + }); + + // process holes from left to right + for (size_t i = 0; i < queue.size(); i++) { + outerNode = eliminateHole(queue[i], outerNode); + } + + return outerNode; +} + +// find a bridge between vertices that connects hole with an outer ring and and link it +template +typename Earcut::Node* +Earcut::eliminateHole(Node* hole, Node* outerNode) { + Node* bridge = findHoleBridge(hole, outerNode); + if (!bridge) { + return outerNode; + } + + Node* bridgeReverse = splitPolygon(bridge, hole); + + // filter collinear points around the cuts + filterPoints(bridgeReverse, bridgeReverse->next); + + // Check if input node was removed by the filtering + return filterPoints(bridge, bridge->next); +} + +// David Eberly's algorithm for finding a bridge between hole and outer polygon +template +typename Earcut::Node* +Earcut::findHoleBridge(Node* hole, Node* outerNode) { + Node* p = outerNode; + double hx = hole->x; + double hy = hole->y; + double qx = -std::numeric_limits::infinity(); + Node* m = nullptr; + + // find a segment intersected by a ray from the hole's leftmost Vertex to the left; + // segment's endpoint with lesser x will be potential connection Vertex + do { + if (hy <= p->y && hy >= p->next->y && p->next->y != p->y) { + double x = p->x + (hy - p->y) * (p->next->x - p->x) / (p->next->y - p->y); + if (x <= hx && x > qx) { + qx = x; + m = p->x < p->next->x ? p : p->next; + if (x == hx) return m; // hole touches outer segment; pick leftmost endpoint + } + } + p = p->next; + } while (p != outerNode); + + if (!m) return 0; + + // look for points inside the triangle of hole Vertex, segment intersection and endpoint; + // if there are no points found, we have a valid connection; + // otherwise choose the Vertex of the minimum angle with the ray as connection Vertex + + const Node* stop = m; + double tanMin = std::numeric_limits::infinity(); + double tanCur = 0; + + p = m; + double mx = m->x; + double my = m->y; + + do { + if (hx >= p->x && p->x >= mx && hx != p->x && + pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p->x, p->y)) { + + tanCur = std::abs(hy - p->y) / (hx - p->x); // tangential + + if (locallyInside(p, hole) && + (tanCur < tanMin || (tanCur == tanMin && (p->x > m->x || sectorContainsSector(m, p))))) { + m = p; + tanMin = tanCur; + } + } + + p = p->next; + } while (p != stop); + + return m; +} + +// whether sector in vertex m contains sector in vertex p in the same coordinates +template +bool Earcut::sectorContainsSector(const Node* m, const Node* p) { + return area(m->prev, m, p->prev) < 0 && area(p->next, m, m->next) < 0; +} + +// interlink polygon nodes in z-order +template +void Earcut::indexCurve(Node* start) { + assert(start); + Node* p = start; + + do { + p->z = p->z ? p->z : zOrder(p->x, p->y); + p->prevZ = p->prev; + p->nextZ = p->next; + p = p->next; + } while (p != start); + + p->prevZ->nextZ = nullptr; + p->prevZ = nullptr; + + sortLinked(p); +} + +// Simon Tatham's linked list merge sort algorithm +// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html +template +typename Earcut::Node* +Earcut::sortLinked(Node* list) { + assert(list); + Node* p; + Node* q; + Node* e; + Node* tail; + int i, numMerges, pSize, qSize; + int inSize = 1; + + for (;;) { + p = list; + list = nullptr; + tail = nullptr; + numMerges = 0; + + while (p) { + numMerges++; + q = p; + pSize = 0; + for (i = 0; i < inSize; i++) { + pSize++; + q = q->nextZ; + if (!q) break; + } + + qSize = inSize; + + while (pSize > 0 || (qSize > 0 && q)) { + + if (pSize == 0) { + e = q; + q = q->nextZ; + qSize--; + } else if (qSize == 0 || !q) { + e = p; + p = p->nextZ; + pSize--; + } else if (p->z <= q->z) { + e = p; + p = p->nextZ; + pSize--; + } else { + e = q; + q = q->nextZ; + qSize--; + } + + if (tail) tail->nextZ = e; + else list = e; + + e->prevZ = tail; + tail = e; + } + + p = q; + } + + tail->nextZ = nullptr; + + if (numMerges <= 1) return list; + + inSize *= 2; + } +} + +// z-order of a Vertex given coords and size of the data bounding box +template +int32_t Earcut::zOrder(const double x_, const double y_) { + // coords are transformed into non-negative 15-bit integer range + int32_t x = static_cast((x_ - minX) * inv_size); + int32_t y = static_cast((y_ - minY) * inv_size); + + x = (x | (x << 8)) & 0x00FF00FF; + x = (x | (x << 4)) & 0x0F0F0F0F; + x = (x | (x << 2)) & 0x33333333; + x = (x | (x << 1)) & 0x55555555; + + y = (y | (y << 8)) & 0x00FF00FF; + y = (y | (y << 4)) & 0x0F0F0F0F; + y = (y | (y << 2)) & 0x33333333; + y = (y | (y << 1)) & 0x55555555; + + return x | (y << 1); +} + +// find the leftmost node of a polygon ring +template +typename Earcut::Node* +Earcut::getLeftmost(Node* start) { + Node* p = start; + Node* leftmost = start; + do { + if (p->x < leftmost->x || (p->x == leftmost->x && p->y < leftmost->y)) + leftmost = p; + p = p->next; + } while (p != start); + + return leftmost; +} + +// check if a point lies within a convex triangle +template +bool Earcut::pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const { + return (cx - px) * (ay - py) >= (ax - px) * (cy - py) && + (ax - px) * (by - py) >= (bx - px) * (ay - py) && + (bx - px) * (cy - py) >= (cx - px) * (by - py); +} + +// check if a diagonal between two polygon nodes is valid (lies in polygon interior) +template +bool Earcut::isValidDiagonal(Node* a, Node* b) { + return a->next->i != b->i && a->prev->i != b->i && !intersectsPolygon(a, b) && // dones't intersect other edges + ((locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible + (area(a->prev, a, b->prev) != 0.0 || area(a, b->prev, b) != 0.0)) || // does not create opposite-facing sectors + (equals(a, b) && area(a->prev, a, a->next) > 0 && area(b->prev, b, b->next) > 0)); // special zero-length case +} + +// signed area of a triangle +template +double Earcut::area(const Node* p, const Node* q, const Node* r) const { + return (q->y - p->y) * (r->x - q->x) - (q->x - p->x) * (r->y - q->y); +} + +// check if two points are equal +template +bool Earcut::equals(const Node* p1, const Node* p2) { + return p1->x == p2->x && p1->y == p2->y; +} + +// check if two segments intersect +template +bool Earcut::intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2) { + int o1 = sign(area(p1, q1, p2)); + int o2 = sign(area(p1, q1, q2)); + int o3 = sign(area(p2, q2, p1)); + int o4 = sign(area(p2, q2, q1)); + + if (o1 != o2 && o3 != o4) return true; // general case + + if (o1 == 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 + if (o2 == 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 + if (o3 == 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 + if (o4 == 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 + + return false; +} + +// for collinear points p, q, r, check if point q lies on segment pr +template +bool Earcut::onSegment(const Node* p, const Node* q, const Node* r) { + return q->x <= std::max(p->x, r->x) && + q->x >= std::min(p->x, r->x) && + q->y <= std::max(p->y, r->y) && + q->y >= std::min(p->y, r->y); +} + +template +int Earcut::sign(double val) { + return (0.0 < val) - (val < 0.0); +} + +// check if a polygon diagonal intersects any polygon segments +template +bool Earcut::intersectsPolygon(const Node* a, const Node* b) { + const Node* p = a; + do { + if (p->i != a->i && p->next->i != a->i && p->i != b->i && p->next->i != b->i && + intersects(p, p->next, a, b)) return true; + p = p->next; + } while (p != a); + + return false; +} + +// check if a polygon diagonal is locally inside the polygon +template +bool Earcut::locallyInside(const Node* a, const Node* b) { + return area(a->prev, a, a->next) < 0 ? + area(a, b, a->next) >= 0 && area(a, a->prev, b) >= 0 : + area(a, b, a->prev) < 0 || area(a, a->next, b) < 0; +} + +// check if the middle Vertex of a polygon diagonal is inside the polygon +template +bool Earcut::middleInside(const Node* a, const Node* b) { + const Node* p = a; + bool inside = false; + double px = (a->x + b->x) / 2; + double py = (a->y + b->y) / 2; + do { + if (((p->y > py) != (p->next->y > py)) && p->next->y != p->y && + (px < (p->next->x - p->x) * (py - p->y) / (p->next->y - p->y) + p->x)) + inside = !inside; + p = p->next; + } while (p != a); + + return inside; +} + +// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits +// polygon into two; if one belongs to the outer ring and another to a hole, it merges it into a +// single ring +template +typename Earcut::Node* +Earcut::splitPolygon(Node* a, Node* b) { + Node* a2 = nodes.construct(a->i, a->x, a->y); + Node* b2 = nodes.construct(b->i, b->x, b->y); + Node* an = a->next; + Node* bp = b->prev; + + a->next = b; + b->prev = a; + + a2->next = an; + an->prev = a2; + + b2->next = a2; + a2->prev = b2; + + bp->next = b2; + b2->prev = bp; + + return b2; +} + +// create a node and util::optionally link it with previous one (in a circular doubly linked list) +template template +typename Earcut::Node* +Earcut::insertNode(std::size_t i, const Point& pt, Node* last) { + Node* p = nodes.construct(static_cast(i), util::nth<0, Point>::get(pt), util::nth<1, Point>::get(pt)); + + if (!last) { + p->prev = p; + p->next = p; + + } else { + assert(last); + p->next = last->next; + p->prev = last; + last->next->prev = p; + last->next = p; + } + return p; +} + +template +void Earcut::removeNode(Node* p) { + p->next->prev = p->prev; + p->prev->next = p->next; + + if (p->prevZ) p->prevZ->nextZ = p->nextZ; + if (p->nextZ) p->nextZ->prevZ = p->prevZ; +} +} + +template +std::vector earcut(const Polygon& poly) { + mapbox::detail::Earcut earcut; + earcut(poly); + return std::move(earcut.indices); +} +} diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/eternal/LICENSE.md b/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/eternal/LICENSE.md new file mode 100644 index 000000000..9bcf13e1e --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/eternal/LICENSE.md @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2018, Mapbox + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/eternal/README.md b/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/eternal/README.md new file mode 100644 index 000000000..559c778f4 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/eternal/README.md @@ -0,0 +1,80 @@ +`eternal.hpp` is a header-only C++ implementation of `constexpr`/compile-time maps and hash maps. It provides an API that is somewhat compatible with `std::map`/`std::unordered_map`, but doesn't support insertion, or other modifications. It's main focus is in **binary size**: it generates minimal code and doesn't incur any static initialization overhead. + +**Why is this useful?** + +- Lookup tables + +**Tested with these compilers/platforms:** +- *Linux GCC 4.9.4* (runtime only, since `constexpr` support is broken in this version) +- Linux GCC 5.5 +- Linux GCC 6.5 +- Linux GCC 7.3 +- Linux GCC 8.1 +- Linux Clang 3.9.1 +- Linux Clang 4 +- Linux Clang 5 +- Linux Clang 6 +- Linux Clang 7 +- macOS Xcode 10.1 +- Android NDK r17+ + +## Usage + +```cpp +MAPBOX_ETERNAL_CONSTEXPR const auto colors = mapbox::eternal::map({ + { "red", { 255, 0, 0, 1 } }, + { "green", { 0, 128, 0, 1 } }, + { "yellow", { 255, 255, 0, 1 } }, + { "white", { 255, 255, 255, 1 } }, + { "black", { 0, 0, 0, 1 } } +}); +``` + +- `mapbox::eternal::map()` is a factory function that produces a `constexpr` map from the `std::pair`s passed to it. +- Alternatively, use `mapbox::eternal::hash_map()` to construct a hash map. The `key` needs a specialization of `std::hash`. +- Use `mapbox::eternal::string` for `constexpr` strings. +- If you need to support GCC 4.9, use `MAPBOX_ETERNAL_CONSTEXPR` instead of `constexpr` in the variable definition. +- You can pass the elements in arbitrary order; they will be sorted at compile time (except for GCC 4.9). To speed up compilation, list the elements in sorted order. To determine the sort order for `hash_map`, iterate over the map and print the result. Note that hashes may be architecture-specific. +- Both `map()` and `hash_map()` support multiple values for the same key. To ensure that keys are unique, run `static_assert(map.unique());` (or equivalent for GCC 4.9) +- The preprocessor variable `MAPBOX_ETERNAL_IS_CONSTEXPR` is set to `1` if `constexpr` construction and lookups are supported. + +## Performance + +Uses a list of 148 unique CSS color names + +``` +Run on (8 X 2600 MHz CPU s) +CPU Caches: + L1 Data 32K (x4) + L1 Instruction 32K (x4) + L2 Unified 262K (x4) + L3 Unified 6291K (x1) +----------------------------------------------------------------------------- +Benchmark Time CPU Iterations +----------------------------------------------------------------------------- +EternalMap_ConstexprLookup 2 ns 2 ns 424582090 +EternalMap_Lookup 48 ns 48 ns 14494194 +EternalMap_LookupMissing 35 ns 35 ns 20442492 +EternalMap_LookupEqualRange 78 ns 78 ns 8862891 + +EternalHashMap_ConstexprLookup 2 ns 2 ns 429076688 +EternalHashMap_Lookup 30 ns 30 ns 22685952 +EternalHashMap_LookupMissing 17 ns 17 ns 39521898 +EternalHashMap_LookupEqualRange 43 ns 43 ns 16696442 + +StdMap_Lookup 51 ns 50 ns 13580630 +StdMap_LookupMissing 58 ns 58 ns 11868028 +StdMap_LookupEqualRange 89 ns 89 ns 7766129 + +StdMultimap_Lookup 50 ns 50 ns 14262312 +StdMultimap_LookupMissing 60 ns 59 ns 11555922 +StdMultimap_LookupEqualRange 103 ns 103 ns 6783077 + +StdUnorderedMap_Lookup 43 ns 43 ns 16175696 +StdUnorderedMap_LookupMissing 50 ns 50 ns 10000000 +StdUnorderedMap_LookupEqualRange 57 ns 57 ns 12256189 + +StdUnorderedMultimap_Lookup 43 ns 43 ns 16011528 +StdUnorderedMultimap_LookupMissing 52 ns 51 ns 10000000 +StdUnorderedMultimap_LookupEqualRange 61 ns 60 ns 11519221 +``` diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/eternal/include/mapbox/eternal.hpp b/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/eternal/include/mapbox/eternal.hpp new file mode 100644 index 000000000..261da6e09 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/mapbox/eternal/include/mapbox/eternal.hpp @@ -0,0 +1,410 @@ +#pragma once + +#include +#include +#include + +// GCC 4.9 compatibility +// GCC < 5.5 also fails to compile with `constexpr` +#if !defined(__clang__) && defined(__GNUC__) && ((__GNUC__ < 5) || (__GNUC__ == 5) && (__GNUC_MINOR__ < 5)) + +#define MAPBOX_ETERNAL_IS_CONSTEXPR 0 +#define MAPBOX_ETERNAL_CONSTEXPR + +#else + +#define MAPBOX_ETERNAL_IS_CONSTEXPR 1 +#define MAPBOX_ETERNAL_CONSTEXPR constexpr + +#endif + +namespace mapbox { +namespace eternal { +namespace impl { + +template +constexpr void swap(T& a, T& b) noexcept { + T tmp{ a }; + a = b; + b = tmp; +} + +template +class compare_key { +public: + const Key key; + + constexpr compare_key(const Key& key_) noexcept : key(key_) { + } + + template + constexpr bool operator<(const Element& rhs) const noexcept { + return key < rhs->first; + } +}; + +template +class element { +public: + using key_type = Key; + using mapped_type = Value; + using value_type = std::pair; + using compare_key_type = compare_key; + + constexpr element(const key_type& key, const mapped_type& value) noexcept : pair(key, value) { + } + + constexpr bool operator<(const element& rhs) const noexcept { + return pair.first < rhs.pair.first; + } + + constexpr bool operator<(const compare_key_type& rhs) const noexcept { + return pair.first < rhs.key; + } + + constexpr const auto& operator*() const noexcept { + return pair; + } + + constexpr const auto* operator->() const noexcept { + return &pair; + } + + MAPBOX_ETERNAL_CONSTEXPR void swap(element& rhs) noexcept { + impl::swap(pair.first, rhs.pair.first); + impl::swap(pair.second, rhs.pair.second); + } + +private: + value_type pair; +}; + +template > +class compare_key_hash : public compare_key { + using base_type = compare_key; + +public: + const std::size_t hash; + + constexpr compare_key_hash(const Key& key_) noexcept : base_type(key_), hash(Hasher()(key_)) { + } + + template + constexpr bool operator<(const Element& rhs) const noexcept { + return hash < rhs.hash || (!(rhs.hash < hash) && base_type::operator<(rhs)); + } +}; + +template > +class element_hash : public element { + using base_type = element; + +public: + using key_type = Key; + using mapped_type = Value; + using compare_key_type = compare_key_hash; + + friend compare_key_type; + + constexpr element_hash(const key_type& key, const mapped_type& value) noexcept + : base_type(key, value), hash(Hasher()(key)) { + } + + template + constexpr bool operator<(const T& rhs) const noexcept { + return hash < rhs.hash || (!(rhs.hash < hash) && base_type::operator<(rhs)); + } + + MAPBOX_ETERNAL_CONSTEXPR void swap(element_hash& rhs) noexcept { + impl::swap(hash, rhs.hash); + base_type::swap(rhs); + } + +private: + std::size_t hash; +}; + +} // namespace impl + +template +class iterator { +public: + constexpr iterator(const Element* pos_) noexcept : pos(pos_) { + } + + constexpr bool operator==(const iterator& rhs) const noexcept { + return pos == rhs.pos; + } + + constexpr bool operator!=(const iterator& rhs) const noexcept { + return pos != rhs.pos; + } + + MAPBOX_ETERNAL_CONSTEXPR iterator& operator++() noexcept { + ++pos; + return *this; + } + + MAPBOX_ETERNAL_CONSTEXPR iterator& operator+=(std::size_t i) noexcept { + pos += i; + return *this; + } + + constexpr iterator operator+(std::size_t i) const noexcept { + return pos + i; + } + + MAPBOX_ETERNAL_CONSTEXPR iterator& operator--() noexcept { + --pos; + return *this; + } + + MAPBOX_ETERNAL_CONSTEXPR iterator& operator-=(std::size_t i) noexcept { + pos -= i; + return *this; + } + + constexpr std::size_t operator-(const iterator& rhs) const noexcept { + return std::size_t(pos - rhs.pos); + } + + constexpr const auto& operator*() const noexcept { + return **pos; + } + + constexpr const auto* operator->() const noexcept { + return &**pos; + } + +private: + const Element* pos; +}; + +namespace impl { + +template +MAPBOX_ETERNAL_CONSTEXPR auto bound(Iterator left, Iterator right, const Key& key) noexcept { + std::size_t count = std::size_t(right - left); + while (count > 0) { + const std::size_t step = count / 2; + right = left + step; + if (Compare()(*right, key)) { + left = ++right; + count -= step + 1; + } else { + count = step; + } + } + return left; +} + +struct less { + template + constexpr bool operator()(const A& a, const B& b) const noexcept { + return a < b; + } +}; + +struct greater_equal { + template + constexpr bool operator()(const A& a, const B& b) const noexcept { + return !(b < a); + } +}; + +template +class map { +private: + static_assert(N > 0, "map is empty"); + + Element data_[N]; + + template + MAPBOX_ETERNAL_CONSTEXPR map(const T (&data)[N], std::index_sequence) noexcept + : data_{ { data[I].first, data[I].second }... } { + static_assert(sizeof...(I) == N, "index_sequence has identical length"); + // Yes, this is a bubblesort. It's usually evaluated at compile-time, it's fast for data + // that is already sorted (like static maps), it has a small code size, and it's stable. + for (auto left = data_, right = data_ + N - 1; data_ < right; right = left, left = data_) { + for (auto it = data_; it < right; ++it) { + if (it[1] < it[0]) { + it[0].swap(it[1]); + left = it; + } + } + } + } + + using compare_key_type = typename Element::compare_key_type; + +public: + template + MAPBOX_ETERNAL_CONSTEXPR map(const T (&data)[N]) noexcept : map(data, std::make_index_sequence()) { + } + + using key_type = typename Element::key_type; + using mapped_type = typename Element::mapped_type; + using value_type = typename Element::value_type; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using const_reference = const value_type&; + using const_pointer = const value_type*; + using const_iterator = iterator; + + MAPBOX_ETERNAL_CONSTEXPR bool unique() const noexcept { + for (auto right = data_ + N - 1, it = data_; it < right; ++it) { + if (!(it[0] < it[1])) { + return false; + } + } + return true; + } + + MAPBOX_ETERNAL_CONSTEXPR const mapped_type& at(const key_type& key) const noexcept { + return find(key)->second; + } + + constexpr std::size_t size() const noexcept { + return N; + } + + constexpr const_iterator begin() const noexcept { + return data_; + } + + constexpr const_iterator cbegin() const noexcept { + return begin(); + } + + constexpr const_iterator end() const noexcept { + return data_ + N; + } + + constexpr const_iterator cend() const noexcept { + return end(); + } + + MAPBOX_ETERNAL_CONSTEXPR const_iterator lower_bound(const key_type& key) const noexcept { + return bound(data_, data_ + N, compare_key_type{ key }); + } + + MAPBOX_ETERNAL_CONSTEXPR const_iterator upper_bound(const key_type& key) const noexcept { + return bound(data_, data_ + N, compare_key_type{ key }); + } + + MAPBOX_ETERNAL_CONSTEXPR std::pair equal_range(const key_type& key) const noexcept { + const compare_key_type compare_key{ key }; + auto first = bound(data_, data_ + N, compare_key); + return { first, bound(first, data_ + N, compare_key) }; + } + + MAPBOX_ETERNAL_CONSTEXPR std::size_t count(const key_type& key) const noexcept { + const auto range = equal_range(key); + return range.second - range.first; + } + + MAPBOX_ETERNAL_CONSTEXPR const_iterator find(const key_type& key) const noexcept { + const compare_key_type compare_key{ key }; + auto it = bound(data_, data_ + N, compare_key); + if (it != data_ + N && greater_equal()(*it, compare_key)) { + return it; + } else { + return end(); + } + } + + MAPBOX_ETERNAL_CONSTEXPR bool contains(const key_type& key) const noexcept { + return find(key) != end(); + } +}; + +} // namespace impl + +template +static constexpr auto map(const std::pair (&items)[N]) noexcept { + return impl::map, N>(items); +} + +template +static constexpr auto hash_map(const std::pair (&items)[N]) noexcept { + return impl::map, N>(items); +} + +} // namespace eternal +} // namespace mapbox + +// mapbox::eternal::string + +namespace mapbox { +namespace eternal { +namespace impl { + +// Use different constants for 32 bit vs. 64 bit size_t +constexpr std::size_t hash_offset = + std::conditional_t, + std::integral_constant>::value; +constexpr std::size_t hash_prime = + std::conditional_t, + std::integral_constant>::value; + +// FNV-1a hash +constexpr static std::size_t str_hash(const char* str, + const std::size_t value = hash_offset) noexcept { + return *str ? str_hash(str + 1, (value ^ static_cast(*str)) * hash_prime) : value; +} + +constexpr bool str_less(const char* lhs, const char* rhs) noexcept { + return *lhs && *rhs && *lhs == *rhs ? str_less(lhs + 1, rhs + 1) : *lhs < *rhs; +} + +constexpr bool str_equal(const char* lhs, const char* rhs) noexcept { + return *lhs == *rhs && (*lhs == '\0' || str_equal(lhs + 1, rhs + 1)); +} + +} // namespace impl + +class string { +private: + const char* data_; + +public: + constexpr string(char const* data) noexcept : data_(data) { + } + + constexpr string(const string&) noexcept = default; + constexpr string(string&&) noexcept = default; + MAPBOX_ETERNAL_CONSTEXPR string& operator=(const string&) noexcept = default; + MAPBOX_ETERNAL_CONSTEXPR string& operator=(string&&) noexcept = default; + + constexpr bool operator<(const string& rhs) const noexcept { + return impl::str_less(data_, rhs.data_); + } + + constexpr bool operator==(const string& rhs) const noexcept { + return impl::str_equal(data_, rhs.data_); + } + + constexpr const char* data() const noexcept { + return data_; + } + + constexpr const char* c_str() const noexcept { + return data_; + } +}; + +} // namespace eternal +} // namespace mapbox + +namespace std { + +template <> +struct hash<::mapbox::eternal::string> { + constexpr std::size_t operator()(const ::mapbox::eternal::string& str) const { + return ::mapbox::eternal::impl::str_hash(str.data()); + } +}; + +} // namespace std diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/nanoflann.COPYING b/contrib/tinyusdz/tinyusdz_repo/src/external/nanoflann.COPYING new file mode 100644 index 000000000..bdc4cfec2 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/nanoflann.COPYING @@ -0,0 +1,29 @@ +Software License Agreement (BSD License) + +Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved. +Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved. +Copyright 2011 Jose L. Blanco (joseluisblancoc@gmail.com). All rights reserved. + +THE BSD LICENSE + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/nanoflann.hpp b/contrib/tinyusdz/tinyusdz_repo/src/external/nanoflann.hpp new file mode 100644 index 000000000..07f82ac60 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/nanoflann.hpp @@ -0,0 +1,2737 @@ +/*********************************************************************** + * Software License Agreement (BSD License) + * + * Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved. + * Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved. + * Copyright 2011-2024 Jose Luis Blanco (joseluisblancoc@gmail.com). + * All rights reserved. + * + * THE BSD LICENSE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *************************************************************************/ + +/** \mainpage nanoflann C++ API documentation + * nanoflann is a C++ header-only library for building KD-Trees, mostly + * optimized for 2D or 3D point clouds. + * + * nanoflann does not require compiling or installing, just an + * #include in your code. + * + * See: + * - [Online README](https://github.com/jlblancoc/nanoflann) + * - [C++ API documentation](https://jlblancoc.github.io/nanoflann/) + */ + +// Modified to disable exception + +#pragma once + +#include +#include +#include +#include +#include // for abs() +#include +#include // for abs() +#include // std::reference_wrapper +#include +#include +#include // std::numeric_limits +#include + +#if !defined(NANOFLANN_NO_EXCEPTIONS) +# if defined(_MSC_VER) +# include // for _HAS_EXCEPTIONS +# endif +# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS) +# define NANOFLANN_NO_EXCEPTIONS 0 +# else +# define NANOFLANN_NO_EXCEPTIONS 1 +# endif +#endif + +#if !NANOFLANN_NO_EXCEPTIONS +#include +#endif + +#include +#include + +/** Library version: 0xMmP (M=Major,m=minor,P=patch) */ +#define NANOFLANN_VERSION 0x155 + +// Avoid conflicting declaration of min/max macros in Windows headers +#if !defined(NOMINMAX) && \ + (defined(_WIN32) || defined(_WIN32_) || defined(WIN32) || defined(_WIN64)) +#define NOMINMAX +#ifdef max +#undef max +#undef min +#endif +#endif +// Avoid conflicts with X11 headers +#ifdef None +#undef None +#endif + +namespace nanoflann +{ +/** @addtogroup nanoflann_grp nanoflann C++ library for KD-trees + * @{ */ + +/** the PI constant (required to avoid MSVC missing symbols) */ +template +T pi_const() +{ + return static_cast(3.14159265358979323846); +} + +/** + * Traits if object is resizable and assignable (typically has a resize | assign + * method) + */ +template +struct has_resize : std::false_type +{ +}; + +template +struct has_resize().resize(1), 0)> + : std::true_type +{ +}; + +template +struct has_assign : std::false_type +{ +}; + +template +struct has_assign().assign(1, 0), 0)> + : std::true_type +{ +}; + +/** + * Free function to resize a resizable object + */ +template +inline typename std::enable_if::value, void>::type resize( + Container& c, const size_t nElements) +{ + c.resize(nElements); +} + +/** + * Free function that has no effects on non resizable containers (e.g. + * std::array) It raises an exception if the expected size does not match + */ +template +inline typename std::enable_if::value, void>::type + resize(Container& c, const size_t nElements) +{ + if (nElements != c.size()) { +#if !NANOFLANN_NO_EXCEPTIONS + throw std::logic_error("Try to change the size of a std::array."); +#endif + } +} + +/** + * Free function to assign to a container + */ +template +inline typename std::enable_if::value, void>::type assign( + Container& c, const size_t nElements, const T& value) +{ + c.assign(nElements, value); +} + +/** + * Free function to assign to a std::array + */ +template +inline typename std::enable_if::value, void>::type + assign(Container& c, const size_t nElements, const T& value) +{ + for (size_t i = 0; i < nElements; i++) c[i] = value; +} + +/** @addtogroup result_sets_grp Result set classes + * @{ */ + +/** Result set for KNN searches (N-closest neighbors) */ +template < + typename _DistanceType, typename _IndexType = size_t, + typename _CountType = size_t> +class KNNResultSet +{ + public: + using DistanceType = _DistanceType; + using IndexType = _IndexType; + using CountType = _CountType; + + private: + IndexType* indices; + DistanceType* dists; + CountType capacity; + CountType count; + + public: + explicit KNNResultSet(CountType capacity_) + : indices(nullptr), dists(nullptr), capacity(capacity_), count(0) + { + } + + void init(IndexType* indices_, DistanceType* dists_) + { + indices = indices_; + dists = dists_; + count = 0; + if (capacity) + dists[capacity - 1] = (std::numeric_limits::max)(); + } + + CountType size() const { return count; } + bool empty() const { return count == 0; } + bool full() const { return count == capacity; } + + /** + * Called during search to add an element matching the criteria. + * @return true if the search should be continued, false if the results are + * sufficient + */ + bool addPoint(DistanceType dist, IndexType index) + { + CountType i; + for (i = count; i > 0; --i) + { + /** If defined and two points have the same distance, the one with + * the lowest-index will be returned first. */ +#ifdef NANOFLANN_FIRST_MATCH + if ((dists[i - 1] > dist) || + ((dist == dists[i - 1]) && (indices[i - 1] > index))) + { +#else + if (dists[i - 1] > dist) + { +#endif + if (i < capacity) + { + dists[i] = dists[i - 1]; + indices[i] = indices[i - 1]; + } + } + else + break; + } + if (i < capacity) + { + dists[i] = dist; + indices[i] = index; + } + if (count < capacity) count++; + + // tell caller that the search shall continue + return true; + } + + DistanceType worstDist() const { return dists[capacity - 1]; } +}; + +/** Result set for RKNN searches (N-closest neighbors with a maximum radius) */ +template < + typename _DistanceType, typename _IndexType = size_t, + typename _CountType = size_t> +class RKNNResultSet +{ + public: + using DistanceType = _DistanceType; + using IndexType = _IndexType; + using CountType = _CountType; + + private: + IndexType* indices; + DistanceType* dists; + CountType capacity; + CountType count; + DistanceType maximumSearchDistanceSquared; + + public: + explicit RKNNResultSet( + CountType capacity_, DistanceType maximumSearchDistanceSquared_) + : indices(nullptr), + dists(nullptr), + capacity(capacity_), + count(0), + maximumSearchDistanceSquared(maximumSearchDistanceSquared_) + { + } + + void init(IndexType* indices_, DistanceType* dists_) + { + indices = indices_; + dists = dists_; + count = 0; + if (capacity) dists[capacity - 1] = maximumSearchDistanceSquared; + } + + CountType size() const { return count; } + bool empty() const { return count == 0; } + bool full() const { return count == capacity; } + + /** + * Called during search to add an element matching the criteria. + * @return true if the search should be continued, false if the results are + * sufficient + */ + bool addPoint(DistanceType dist, IndexType index) + { + CountType i; + for (i = count; i > 0; --i) + { + /** If defined and two points have the same distance, the one with + * the lowest-index will be returned first. */ +#ifdef NANOFLANN_FIRST_MATCH + if ((dists[i - 1] > dist) || + ((dist == dists[i - 1]) && (indices[i - 1] > index))) + { +#else + if (dists[i - 1] > dist) + { +#endif + if (i < capacity) + { + dists[i] = dists[i - 1]; + indices[i] = indices[i - 1]; + } + } + else + break; + } + if (i < capacity) + { + dists[i] = dist; + indices[i] = index; + } + if (count < capacity) count++; + + // tell caller that the search shall continue + return true; + } + + DistanceType worstDist() const { return dists[capacity - 1]; } +}; + +/** operator "<" for std::sort() */ +struct IndexDist_Sorter +{ + /** PairType will be typically: ResultItem */ + template + bool operator()(const PairType& p1, const PairType& p2) const + { + return p1.second < p2.second; + } +}; + +/** + * Each result element in RadiusResultSet. Note that distances and indices + * are named `first` and `second` to keep backward-compatibility with the + * `std::pair<>` type used in the past. In contrast, this structure is ensured + * to be `std::is_standard_layout` so it can be used in wrappers to other + * languages. + * See: https://github.com/jlblancoc/nanoflann/issues/166 + */ +template +struct ResultItem +{ + ResultItem() = default; + ResultItem(const IndexType index, const DistanceType distance) + : first(index), second(distance) + { + } + + IndexType first; //!< Index of the sample in the dataset + DistanceType second; //!< Distance from sample to query point +}; + +/** + * A result-set class used when performing a radius based search. + */ +template +class RadiusResultSet +{ + public: + using DistanceType = _DistanceType; + using IndexType = _IndexType; + + public: + const DistanceType radius; + + std::vector>& m_indices_dists; + + explicit RadiusResultSet( + DistanceType radius_, + std::vector>& indices_dists) + : radius(radius_), m_indices_dists(indices_dists) + { + init(); + } + + void init() { clear(); } + void clear() { m_indices_dists.clear(); } + + size_t size() const { return m_indices_dists.size(); } + size_t empty() const { return m_indices_dists.empty(); } + + bool full() const { return true; } + + /** + * Called during search to add an element matching the criteria. + * @return true if the search should be continued, false if the results are + * sufficient + */ + bool addPoint(DistanceType dist, IndexType index) + { + if (dist < radius) m_indices_dists.emplace_back(index, dist); + return true; + } + + DistanceType worstDist() const { return radius; } + + /** + * Find the worst result (farthest neighbor) without copying or sorting + * Pre-conditions: size() > 0 + */ + ResultItem worst_item() const + { + if (m_indices_dists.empty()) { +#if !NANOFLANN_NO_EXCEPTIONS + throw std::runtime_error( + "Cannot invoke RadiusResultSet::worst_item() on " + "an empty list of results."); +#endif + } + auto it = std::max_element( + m_indices_dists.begin(), m_indices_dists.end(), IndexDist_Sorter()); + return *it; + } +}; + +/** @} */ + +/** @addtogroup loadsave_grp Load/save auxiliary functions + * @{ */ +template +void save_value(std::ostream& stream, const T& value) +{ + stream.write(reinterpret_cast(&value), sizeof(T)); +} + +template +void save_value(std::ostream& stream, const std::vector& value) +{ + size_t size = value.size(); + stream.write(reinterpret_cast(&size), sizeof(size_t)); + stream.write(reinterpret_cast(value.data()), sizeof(T) * size); +} + +template +void load_value(std::istream& stream, T& value) +{ + stream.read(reinterpret_cast(&value), sizeof(T)); +} + +template +void load_value(std::istream& stream, std::vector& value) +{ + size_t size; + stream.read(reinterpret_cast(&size), sizeof(size_t)); + value.resize(size); + stream.read(reinterpret_cast(value.data()), sizeof(T) * size); +} +/** @} */ + +/** @addtogroup metric_grp Metric (distance) classes + * @{ */ + +struct Metric +{ +}; + +/** Manhattan distance functor (generic version, optimized for + * high-dimensionality data sets). Corresponding distance traits: + * nanoflann::metric_L1 + * + * \tparam T Type of the elements (e.g. double, float, uint8_t) + * \tparam DataSource Source of the data, i.e. where the vectors are stored + * \tparam _DistanceType Type of distance variables (must be signed) + * \tparam IndexType Type of the arguments with which the data can be + * accessed (e.g. float, double, int64_t, T*) + */ +template < + class T, class DataSource, typename _DistanceType = T, + typename IndexType = uint32_t> +struct L1_Adaptor +{ + using ElementType = T; + using DistanceType = _DistanceType; + + const DataSource& data_source; + + L1_Adaptor(const DataSource& _data_source) : data_source(_data_source) {} + + DistanceType evalMetric( + const T* a, const IndexType b_idx, size_t size, + DistanceType worst_dist = -1) const + { + DistanceType result = DistanceType(); + const T* last = a + size; + const T* lastgroup = last - 3; + size_t d = 0; + + /* Process 4 items with each loop for efficiency. */ + while (a < lastgroup) + { + const DistanceType diff0 = + std::abs(a[0] - data_source.kdtree_get_pt(b_idx, d++)); + const DistanceType diff1 = + std::abs(a[1] - data_source.kdtree_get_pt(b_idx, d++)); + const DistanceType diff2 = + std::abs(a[2] - data_source.kdtree_get_pt(b_idx, d++)); + const DistanceType diff3 = + std::abs(a[3] - data_source.kdtree_get_pt(b_idx, d++)); + result += diff0 + diff1 + diff2 + diff3; + a += 4; + if ((worst_dist > 0) && (result > worst_dist)) { return result; } + } + /* Process last 0-3 components. Not needed for standard vector lengths. + */ + while (a < last) + { + result += std::abs(*a++ - data_source.kdtree_get_pt(b_idx, d++)); + } + return result; + } + + template + DistanceType accum_dist(const U a, const V b, const size_t) const + { + return std::abs(a - b); + } +}; + +/** **Squared** Euclidean distance functor (generic version, optimized for + * high-dimensionality data sets). Corresponding distance traits: + * nanoflann::metric_L2 + * + * \tparam T Type of the elements (e.g. double, float, uint8_t) + * \tparam DataSource Source of the data, i.e. where the vectors are stored + * \tparam _DistanceType Type of distance variables (must be signed) + * \tparam IndexType Type of the arguments with which the data can be + * accessed (e.g. float, double, int64_t, T*) + */ +template < + class T, class DataSource, typename _DistanceType = T, + typename IndexType = uint32_t> +struct L2_Adaptor +{ + using ElementType = T; + using DistanceType = _DistanceType; + + const DataSource& data_source; + + L2_Adaptor(const DataSource& _data_source) : data_source(_data_source) {} + + DistanceType evalMetric( + const T* a, const IndexType b_idx, size_t size, + DistanceType worst_dist = -1) const + { + DistanceType result = DistanceType(); + const T* last = a + size; + const T* lastgroup = last - 3; + size_t d = 0; + + /* Process 4 items with each loop for efficiency. */ + while (a < lastgroup) + { + const DistanceType diff0 = + a[0] - data_source.kdtree_get_pt(b_idx, d++); + const DistanceType diff1 = + a[1] - data_source.kdtree_get_pt(b_idx, d++); + const DistanceType diff2 = + a[2] - data_source.kdtree_get_pt(b_idx, d++); + const DistanceType diff3 = + a[3] - data_source.kdtree_get_pt(b_idx, d++); + result += + diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3; + a += 4; + if ((worst_dist > 0) && (result > worst_dist)) { return result; } + } + /* Process last 0-3 components. Not needed for standard vector lengths. + */ + while (a < last) + { + const DistanceType diff0 = + *a++ - data_source.kdtree_get_pt(b_idx, d++); + result += diff0 * diff0; + } + return result; + } + + template + DistanceType accum_dist(const U a, const V b, const size_t) const + { + return (a - b) * (a - b); + } +}; + +/** **Squared** Euclidean (L2) distance functor (suitable for low-dimensionality + * datasets, like 2D or 3D point clouds) Corresponding distance traits: + * nanoflann::metric_L2_Simple + * + * \tparam T Type of the elements (e.g. double, float, uint8_t) + * \tparam DataSource Source of the data, i.e. where the vectors are stored + * \tparam _DistanceType Type of distance variables (must be signed) + * \tparam IndexType Type of the arguments with which the data can be + * accessed (e.g. float, double, int64_t, T*) + */ +template < + class T, class DataSource, typename _DistanceType = T, + typename IndexType = uint32_t> +struct L2_Simple_Adaptor +{ + using ElementType = T; + using DistanceType = _DistanceType; + + const DataSource& data_source; + + L2_Simple_Adaptor(const DataSource& _data_source) + : data_source(_data_source) + { + } + + DistanceType evalMetric( + const T* a, const IndexType b_idx, size_t size) const + { + DistanceType result = DistanceType(); + for (size_t i = 0; i < size; ++i) + { + const DistanceType diff = + a[i] - data_source.kdtree_get_pt(b_idx, i); + result += diff * diff; + } + return result; + } + + template + DistanceType accum_dist(const U a, const V b, const size_t) const + { + return (a - b) * (a - b); + } +}; + +/** SO2 distance functor + * Corresponding distance traits: nanoflann::metric_SO2 + * + * \tparam T Type of the elements (e.g. double, float, uint8_t) + * \tparam DataSource Source of the data, i.e. where the vectors are stored + * \tparam _DistanceType Type of distance variables (must be signed) (e.g. + * float, double) orientation is constrained to be in [-pi, pi] + * \tparam IndexType Type of the arguments with which the data can be + * accessed (e.g. float, double, int64_t, T*) + */ +template < + class T, class DataSource, typename _DistanceType = T, + typename IndexType = uint32_t> +struct SO2_Adaptor +{ + using ElementType = T; + using DistanceType = _DistanceType; + + const DataSource& data_source; + + SO2_Adaptor(const DataSource& _data_source) : data_source(_data_source) {} + + DistanceType evalMetric( + const T* a, const IndexType b_idx, size_t size) const + { + return accum_dist( + a[size - 1], data_source.kdtree_get_pt(b_idx, size - 1), size - 1); + } + + /** Note: this assumes that input angles are already in the range [-pi,pi] + */ + template + DistanceType accum_dist(const U a, const V b, const size_t) const + { + DistanceType result = DistanceType(); + DistanceType PI = pi_const(); + result = b - a; + if (result > PI) + result -= 2 * PI; + else if (result < -PI) + result += 2 * PI; + return result; + } +}; + +/** SO3 distance functor (Uses L2_Simple) + * Corresponding distance traits: nanoflann::metric_SO3 + * + * \tparam T Type of the elements (e.g. double, float, uint8_t) + * \tparam DataSource Source of the data, i.e. where the vectors are stored + * \tparam _DistanceType Type of distance variables (must be signed) (e.g. + * float, double) + * \tparam IndexType Type of the arguments with which the data can be + * accessed (e.g. float, double, int64_t, T*) + */ +template < + class T, class DataSource, typename _DistanceType = T, + typename IndexType = uint32_t> +struct SO3_Adaptor +{ + using ElementType = T; + using DistanceType = _DistanceType; + + L2_Simple_Adaptor + distance_L2_Simple; + + SO3_Adaptor(const DataSource& _data_source) + : distance_L2_Simple(_data_source) + { + } + + DistanceType evalMetric( + const T* a, const IndexType b_idx, size_t size) const + { + return distance_L2_Simple.evalMetric(a, b_idx, size); + } + + template + DistanceType accum_dist(const U a, const V b, const size_t idx) const + { + return distance_L2_Simple.accum_dist(a, b, idx); + } +}; + +/** Metaprogramming helper traits class for the L1 (Manhattan) metric */ +struct metric_L1 : public Metric +{ + template + struct traits + { + using distance_t = L1_Adaptor; + }; +}; +/** Metaprogramming helper traits class for the L2 (Euclidean) **squared** + * distance metric */ +struct metric_L2 : public Metric +{ + template + struct traits + { + using distance_t = L2_Adaptor; + }; +}; +/** Metaprogramming helper traits class for the L2_simple (Euclidean) + * **squared** distance metric */ +struct metric_L2_Simple : public Metric +{ + template + struct traits + { + using distance_t = L2_Simple_Adaptor; + }; +}; +/** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */ +struct metric_SO2 : public Metric +{ + template + struct traits + { + using distance_t = SO2_Adaptor; + }; +}; +/** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */ +struct metric_SO3 : public Metric +{ + template + struct traits + { + using distance_t = SO3_Adaptor; + }; +}; + +/** @} */ + +/** @addtogroup param_grp Parameter structs + * @{ */ + +enum class KDTreeSingleIndexAdaptorFlags +{ + None = 0, + SkipInitialBuildIndex = 1 +}; + +inline std::underlying_type::type operator&( + KDTreeSingleIndexAdaptorFlags lhs, KDTreeSingleIndexAdaptorFlags rhs) +{ + using underlying = + typename std::underlying_type::type; + return static_cast(lhs) & static_cast(rhs); +} + +/** Parameters (see README.md) */ +struct KDTreeSingleIndexAdaptorParams +{ + KDTreeSingleIndexAdaptorParams( + size_t _leaf_max_size = 10, + KDTreeSingleIndexAdaptorFlags _flags = + KDTreeSingleIndexAdaptorFlags::None, + unsigned int _n_thread_build = 1) + : leaf_max_size(_leaf_max_size), + flags(_flags), + n_thread_build(_n_thread_build) + { + } + + size_t leaf_max_size; + KDTreeSingleIndexAdaptorFlags flags; + unsigned int n_thread_build; +}; + +/** Search options for KDTreeSingleIndexAdaptor::findNeighbors() */ +struct SearchParameters +{ + SearchParameters(float eps_ = 0, bool sorted_ = true) + : eps(eps_), sorted(sorted_) + { + } + + float eps; //!< search for eps-approximate neighbours (default: 0) + bool sorted; //!< only for radius search, require neighbours sorted by + //!< distance (default: true) +}; +/** @} */ + +/** @addtogroup memalloc_grp Memory allocation + * @{ */ + +/** + * Pooled storage allocator + * + * The following routines allow for the efficient allocation of storage in + * small chunks from a specified pool. Rather than allowing each structure + * to be freed individually, an entire pool of storage is freed at once. + * This method has two advantages over just using malloc() and free(). First, + * it is far more efficient for allocating small objects, as there is + * no overhead for remembering all the information needed to free each + * object or consolidating fragmented memory. Second, the decision about + * how long to keep an object is made at the time of allocation, and there + * is no need to track down all the objects to free them. + * + */ +class PooledAllocator +{ + static constexpr size_t WORDSIZE = 16; // WORDSIZE must >= 8 + static constexpr size_t BLOCKSIZE = 8192; + + /* We maintain memory alignment to word boundaries by requiring that all + allocations be in multiples of the machine wordsize. */ + /* Size of machine word in bytes. Must be power of 2. */ + /* Minimum number of bytes requested at a time from the system. Must be + * multiple of WORDSIZE. */ + + using Size = size_t; + + Size remaining_ = 0; //!< Number of bytes left in current block of storage + void* base_ = nullptr; //!< Pointer to base of current block of storage + void* loc_ = nullptr; //!< Current location in block to next allocate + + void internal_init() + { + remaining_ = 0; + base_ = nullptr; + usedMemory = 0; + wastedMemory = 0; + } + + public: + Size usedMemory = 0; + Size wastedMemory = 0; + + /** + Default constructor. Initializes a new pool. + */ + PooledAllocator() { internal_init(); } + + /** + * Destructor. Frees all the memory allocated in this pool. + */ + ~PooledAllocator() { free_all(); } + + /** Frees all allocated memory chunks */ + void free_all() + { + while (base_ != nullptr) + { + // Get pointer to prev block + void* prev = *(static_cast(base_)); + ::free(base_); + base_ = prev; + } + internal_init(); + } + + /** + * Returns a pointer to a piece of new memory of the given size in bytes + * allocated from the pool. + */ + void* malloc(const size_t req_size) + { + /* Round size up to a multiple of wordsize. The following expression + only works for WORDSIZE that is a power of 2, by masking last bits + of incremented size to zero. + */ + const Size size = (req_size + (WORDSIZE - 1)) & ~(WORDSIZE - 1); + + /* Check whether a new block must be allocated. Note that the first + word of a block is reserved for a pointer to the previous block. + */ + if (size > remaining_) + { + wastedMemory += remaining_; + + /* Allocate new storage. */ + const Size blocksize = + size > BLOCKSIZE ? size + WORDSIZE : BLOCKSIZE + WORDSIZE; + + // use the standard C malloc to allocate memory + void* m = ::malloc(blocksize); + if (!m) + { + fprintf(stderr, "Failed to allocate memory.\n"); +#if !NANOFLANN_NO_EXCEPTIONS + throw std::bad_alloc(); +#endif + } + + /* Fill first word of new block with pointer to previous block. */ + static_cast(m)[0] = base_; + base_ = m; + + remaining_ = blocksize - WORDSIZE; + loc_ = static_cast(m) + WORDSIZE; + } + void* rloc = loc_; + loc_ = static_cast(loc_) + size; + remaining_ -= size; + + usedMemory += size; + + return rloc; + } + + /** + * Allocates (using this pool) a generic type T. + * + * Params: + * count = number of instances to allocate. + * Returns: pointer (of type T*) to memory buffer + */ + template + T* allocate(const size_t count = 1) + { + T* mem = static_cast(this->malloc(sizeof(T) * count)); + return mem; + } +}; +/** @} */ + +/** @addtogroup nanoflann_metaprog_grp Auxiliary metaprogramming stuff + * @{ */ + +/** Used to declare fixed-size arrays when DIM>0, dynamically-allocated vectors + * when DIM=-1. Fixed size version for a generic DIM: + */ +template +struct array_or_vector +{ + using type = std::array; +}; +/** Dynamic size version */ +template +struct array_or_vector<-1, T> +{ + using type = std::vector; +}; + +/** @} */ + +/** kd-tree base-class + * + * Contains the member functions common to the classes KDTreeSingleIndexAdaptor + * and KDTreeSingleIndexDynamicAdaptor_. + * + * \tparam Derived The name of the class which inherits this class. + * \tparam DatasetAdaptor The user-provided adaptor, which must be ensured to + * have a lifetime equal or longer than the instance of this class. + * \tparam Distance The distance metric to use, these are all classes derived + * from nanoflann::Metric + * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) + * \tparam IndexType Type of the arguments with which the data can be + * accessed (e.g. float, double, int64_t, T*) + */ +template < + class Derived, typename Distance, class DatasetAdaptor, int32_t DIM = -1, + typename IndexType = uint32_t> +class KDTreeBaseClass +{ + public: + /** Frees the previously-built index. Automatically called within + * buildIndex(). */ + void freeIndex(Derived& obj) + { + obj.pool_.free_all(); + obj.root_node_ = nullptr; + obj.size_at_index_build_ = 0; + } + + using ElementType = typename Distance::ElementType; + using DistanceType = typename Distance::DistanceType; + + /** + * Array of indices to vectors in the dataset_. + */ + std::vector vAcc_; + + using Offset = typename decltype(vAcc_)::size_type; + using Size = typename decltype(vAcc_)::size_type; + using Dimension = int32_t; + + /*--------------------------- + * Internal Data Structures + * --------------------------*/ + struct Node + { + /** Union used because a node can be either a LEAF node or a non-leaf + * node, so both data fields are never used simultaneously */ + union + { + struct leaf + { + Offset left, right; //!< Indices of points in leaf node + } lr; + struct nonleaf + { + Dimension divfeat; //!< Dimension used for subdivision. + /// The values used for subdivision. + DistanceType divlow, divhigh; + } sub; + } node_type; + + /** Child nodes (both=nullptr mean its a leaf node) */ + Node *child1 = nullptr, *child2 = nullptr; + }; + + using NodePtr = Node*; + using NodeConstPtr = const Node*; + + struct Interval + { + ElementType low, high; + }; + + NodePtr root_node_ = nullptr; + + Size leaf_max_size_ = 0; + + /// Number of thread for concurrent tree build + Size n_thread_build_ = 1; + /// Number of current points in the dataset + Size size_ = 0; + /// Number of points in the dataset when the index was built + Size size_at_index_build_ = 0; + Dimension dim_ = 0; //!< Dimensionality of each data point + + /** Define "BoundingBox" as a fixed-size or variable-size container + * depending on "DIM" */ + using BoundingBox = typename array_or_vector::type; + + /** Define "distance_vector_t" as a fixed-size or variable-size container + * depending on "DIM" */ + using distance_vector_t = typename array_or_vector::type; + + /** The KD-tree used to find neighbours */ + BoundingBox root_bbox_; + + /** + * Pooled memory allocator. + * + * Using a pooled memory allocator is more efficient + * than allocating memory directly when there is a large + * number small of memory allocations. + */ + PooledAllocator pool_; + + /** Returns number of points in dataset */ + Size size(const Derived& obj) const { return obj.size_; } + + /** Returns the length of each point in the dataset */ + Size veclen(const Derived& obj) { return DIM > 0 ? DIM : obj.dim; } + + /// Helper accessor to the dataset points: + ElementType dataset_get( + const Derived& obj, IndexType element, Dimension component) const + { + return obj.dataset_.kdtree_get_pt(element, component); + } + + /** + * Computes the inde memory usage + * Returns: memory used by the index + */ + Size usedMemory(Derived& obj) + { + return obj.pool_.usedMemory + obj.pool_.wastedMemory + + obj.dataset_.kdtree_get_point_count() * + sizeof(IndexType); // pool memory and vind array memory + } + + void computeMinMax( + const Derived& obj, Offset ind, Size count, Dimension element, + ElementType& min_elem, ElementType& max_elem) + { + min_elem = dataset_get(obj, vAcc_[ind], element); + max_elem = min_elem; + for (Offset i = 1; i < count; ++i) + { + ElementType val = dataset_get(obj, vAcc_[ind + i], element); + if (val < min_elem) min_elem = val; + if (val > max_elem) max_elem = val; + } + } + + /** + * Create a tree node that subdivides the list of vecs from vind[first] + * to vind[last]. The routine is called recursively on each sublist. + * + * @param left index of the first vector + * @param right index of the last vector + */ + NodePtr divideTree( + Derived& obj, const Offset left, const Offset right, BoundingBox& bbox) + { + NodePtr node = obj.pool_.template allocate(); // allocate memory + const auto dims = (DIM > 0 ? DIM : obj.dim_); + + /* If too few exemplars remain, then make this a leaf node. */ + if ((right - left) <= static_cast(obj.leaf_max_size_)) + { + node->child1 = node->child2 = nullptr; /* Mark as leaf node. */ + node->node_type.lr.left = left; + node->node_type.lr.right = right; + + // compute bounding-box of leaf points + for (Dimension i = 0; i < dims; ++i) + { + bbox[i].low = dataset_get(obj, obj.vAcc_[left], i); + bbox[i].high = dataset_get(obj, obj.vAcc_[left], i); + } + for (Offset k = left + 1; k < right; ++k) + { + for (Dimension i = 0; i < dims; ++i) + { + const auto val = dataset_get(obj, obj.vAcc_[k], i); + if (bbox[i].low > val) bbox[i].low = val; + if (bbox[i].high < val) bbox[i].high = val; + } + } + } + else + { + Offset idx; + Dimension cutfeat; + DistanceType cutval; + middleSplit_(obj, left, right - left, idx, cutfeat, cutval, bbox); + + node->node_type.sub.divfeat = cutfeat; + + BoundingBox left_bbox(bbox); + left_bbox[cutfeat].high = cutval; + node->child1 = this->divideTree(obj, left, left + idx, left_bbox); + + BoundingBox right_bbox(bbox); + right_bbox[cutfeat].low = cutval; + node->child2 = this->divideTree(obj, left + idx, right, right_bbox); + + node->node_type.sub.divlow = left_bbox[cutfeat].high; + node->node_type.sub.divhigh = right_bbox[cutfeat].low; + + for (Dimension i = 0; i < dims; ++i) + { + bbox[i].low = std::min(left_bbox[i].low, right_bbox[i].low); + bbox[i].high = std::max(left_bbox[i].high, right_bbox[i].high); + } + } + + return node; + } + + /** + * Create a tree node that subdivides the list of vecs from vind[first] to + * vind[last] concurrently. The routine is called recursively on each + * sublist. + * + * @param left index of the first vector + * @param right index of the last vector + * @param thread_count count of std::async threads + * @param mutex mutex for mempool allocation + */ + NodePtr divideTreeConcurrent( + Derived& obj, const Offset left, const Offset right, BoundingBox& bbox, + std::atomic& thread_count, std::mutex& mutex) + { + std::unique_lock lock(mutex); + NodePtr node = obj.pool_.template allocate(); // allocate memory + lock.unlock(); + + const auto dims = (DIM > 0 ? DIM : obj.dim_); + + /* If too few exemplars remain, then make this a leaf node. */ + if ((right - left) <= static_cast(obj.leaf_max_size_)) + { + node->child1 = node->child2 = nullptr; /* Mark as leaf node. */ + node->node_type.lr.left = left; + node->node_type.lr.right = right; + + // compute bounding-box of leaf points + for (Dimension i = 0; i < dims; ++i) + { + bbox[i].low = dataset_get(obj, obj.vAcc_[left], i); + bbox[i].high = dataset_get(obj, obj.vAcc_[left], i); + } + for (Offset k = left + 1; k < right; ++k) + { + for (Dimension i = 0; i < dims; ++i) + { + const auto val = dataset_get(obj, obj.vAcc_[k], i); + if (bbox[i].low > val) bbox[i].low = val; + if (bbox[i].high < val) bbox[i].high = val; + } + } + } + else + { + Offset idx; + Dimension cutfeat; + DistanceType cutval; + middleSplit_(obj, left, right - left, idx, cutfeat, cutval, bbox); + + node->node_type.sub.divfeat = cutfeat; + + std::future left_future, right_future; + + BoundingBox left_bbox(bbox); + left_bbox[cutfeat].high = cutval; + if (++thread_count < n_thread_build_) + { + left_future = std::async( + std::launch::async, &KDTreeBaseClass::divideTreeConcurrent, + this, std::ref(obj), left, left + idx, std::ref(left_bbox), + std::ref(thread_count), std::ref(mutex)); + } + else + { + --thread_count; + node->child1 = this->divideTreeConcurrent( + obj, left, left + idx, left_bbox, thread_count, mutex); + } + + BoundingBox right_bbox(bbox); + right_bbox[cutfeat].low = cutval; + if (++thread_count < n_thread_build_) + { + right_future = std::async( + std::launch::async, &KDTreeBaseClass::divideTreeConcurrent, + this, std::ref(obj), left + idx, right, + std::ref(right_bbox), std::ref(thread_count), + std::ref(mutex)); + } + else + { + --thread_count; + node->child2 = this->divideTreeConcurrent( + obj, left + idx, right, right_bbox, thread_count, mutex); + } + + if (left_future.valid()) + { + node->child1 = left_future.get(); + --thread_count; + } + if (right_future.valid()) + { + node->child2 = right_future.get(); + --thread_count; + } + + node->node_type.sub.divlow = left_bbox[cutfeat].high; + node->node_type.sub.divhigh = right_bbox[cutfeat].low; + + for (Dimension i = 0; i < dims; ++i) + { + bbox[i].low = std::min(left_bbox[i].low, right_bbox[i].low); + bbox[i].high = std::max(left_bbox[i].high, right_bbox[i].high); + } + } + + return node; + } + + void middleSplit_( + const Derived& obj, const Offset ind, const Size count, Offset& index, + Dimension& cutfeat, DistanceType& cutval, const BoundingBox& bbox) + { + const auto dims = (DIM > 0 ? DIM : obj.dim_); + const auto EPS = static_cast(0.00001); + ElementType max_span = bbox[0].high - bbox[0].low; + for (Dimension i = 1; i < dims; ++i) + { + ElementType span = bbox[i].high - bbox[i].low; + if (span > max_span) { max_span = span; } + } + ElementType max_spread = -1; + cutfeat = 0; + ElementType min_elem = 0, max_elem = 0; + for (Dimension i = 0; i < dims; ++i) + { + ElementType span = bbox[i].high - bbox[i].low; + if (span > (1 - EPS) * max_span) + { + ElementType min_elem_, max_elem_; + computeMinMax(obj, ind, count, i, min_elem_, max_elem_); + ElementType spread = max_elem_ - min_elem_; + if (spread > max_spread) + { + cutfeat = i; + max_spread = spread; + min_elem = min_elem_; + max_elem = max_elem_; + } + } + } + // split in the middle + DistanceType split_val = (bbox[cutfeat].low + bbox[cutfeat].high) / 2; + + if (split_val < min_elem) + cutval = min_elem; + else if (split_val > max_elem) + cutval = max_elem; + else + cutval = split_val; + + Offset lim1, lim2; + planeSplit(obj, ind, count, cutfeat, cutval, lim1, lim2); + + if (lim1 > count / 2) + index = lim1; + else if (lim2 < count / 2) + index = lim2; + else + index = count / 2; + } + + /** + * Subdivide the list of points by a plane perpendicular on the axis + * corresponding to the 'cutfeat' dimension at 'cutval' position. + * + * On return: + * dataset[ind[0..lim1-1]][cutfeat]cutval + */ + void planeSplit( + const Derived& obj, const Offset ind, const Size count, + const Dimension cutfeat, const DistanceType& cutval, Offset& lim1, + Offset& lim2) + { + /* Move vector indices for left subtree to front of list. */ + Offset left = 0; + Offset right = count - 1; + for (;;) + { + while (left <= right && + dataset_get(obj, vAcc_[ind + left], cutfeat) < cutval) + ++left; + while (right && left <= right && + dataset_get(obj, vAcc_[ind + right], cutfeat) >= cutval) + --right; + if (left > right || !right) + break; // "!right" was added to support unsigned Index types + std::swap(vAcc_[ind + left], vAcc_[ind + right]); + ++left; + --right; + } + /* If either list is empty, it means that all remaining features + * are identical. Split in the middle to maintain a balanced tree. + */ + lim1 = left; + right = count - 1; + for (;;) + { + while (left <= right && + dataset_get(obj, vAcc_[ind + left], cutfeat) <= cutval) + ++left; + while (right && left <= right && + dataset_get(obj, vAcc_[ind + right], cutfeat) > cutval) + --right; + if (left > right || !right) + break; // "!right" was added to support unsigned Index types + std::swap(vAcc_[ind + left], vAcc_[ind + right]); + ++left; + --right; + } + lim2 = left; + } + + DistanceType computeInitialDistances( + const Derived& obj, const ElementType* vec, + distance_vector_t& dists) const + { + assert(vec); + DistanceType dist = DistanceType(); + + for (Dimension i = 0; i < (DIM > 0 ? DIM : obj.dim_); ++i) + { + if (vec[i] < obj.root_bbox_[i].low) + { + dists[i] = + obj.distance_.accum_dist(vec[i], obj.root_bbox_[i].low, i); + dist += dists[i]; + } + if (vec[i] > obj.root_bbox_[i].high) + { + dists[i] = + obj.distance_.accum_dist(vec[i], obj.root_bbox_[i].high, i); + dist += dists[i]; + } + } + return dist; + } + + static void save_tree( + const Derived& obj, std::ostream& stream, const NodeConstPtr tree) + { + save_value(stream, *tree); + if (tree->child1 != nullptr) { save_tree(obj, stream, tree->child1); } + if (tree->child2 != nullptr) { save_tree(obj, stream, tree->child2); } + } + + static void load_tree(Derived& obj, std::istream& stream, NodePtr& tree) + { + tree = obj.pool_.template allocate(); + load_value(stream, *tree); + if (tree->child1 != nullptr) { load_tree(obj, stream, tree->child1); } + if (tree->child2 != nullptr) { load_tree(obj, stream, tree->child2); } + } + + /** Stores the index in a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so + * when loading the index object it must be constructed associated to the + * same source of data points used while building it. See the example: + * examples/saveload_example.cpp \sa loadIndex */ + void saveIndex(const Derived& obj, std::ostream& stream) const + { + save_value(stream, obj.size_); + save_value(stream, obj.dim_); + save_value(stream, obj.root_bbox_); + save_value(stream, obj.leaf_max_size_); + save_value(stream, obj.vAcc_); + if (obj.root_node_) save_tree(obj, stream, obj.root_node_); + } + + /** Loads a previous index from a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so + * the index object must be constructed associated to the same source of + * data points used while building the index. See the example: + * examples/saveload_example.cpp \sa loadIndex */ + void loadIndex(Derived& obj, std::istream& stream) + { + load_value(stream, obj.size_); + load_value(stream, obj.dim_); + load_value(stream, obj.root_bbox_); + load_value(stream, obj.leaf_max_size_); + load_value(stream, obj.vAcc_); + load_tree(obj, stream, obj.root_node_); + } +}; + +/** @addtogroup kdtrees_grp KD-tree classes and adaptors + * @{ */ + +/** kd-tree static index + * + * Contains the k-d trees and other information for indexing a set of points + * for nearest-neighbor matching. + * + * The class "DatasetAdaptor" must provide the following interface (can be + * non-virtual, inlined methods): + * + * \code + * // Must return the number of data poins + * size_t kdtree_get_point_count() const { ... } + * + * + * // Must return the dim'th component of the idx'th point in the class: + * T kdtree_get_pt(const size_t idx, const size_t dim) const { ... } + * + * // Optional bounding-box computation: return false to default to a standard + * bbox computation loop. + * // Return true if the BBOX was already computed by the class and returned + * in "bb" so it can be avoided to redo it again. + * // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 + * for point clouds) template bool kdtree_get_bbox(BBOX &bb) const + * { + * bb[0].low = ...; bb[0].high = ...; // 0th dimension limits + * bb[1].low = ...; bb[1].high = ...; // 1st dimension limits + * ... + * return true; + * } + * + * \endcode + * + * \tparam DatasetAdaptor The user-provided adaptor, which must be ensured to + * have a lifetime equal or longer than the instance of this class. + * \tparam Distance The distance metric to use: nanoflann::metric_L1, + * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. \tparam DIM + * Dimensionality of data points (e.g. 3 for 3D points) \tparam IndexType Will + * be typically size_t or int + */ +template < + typename Distance, class DatasetAdaptor, int32_t DIM = -1, + typename IndexType = uint32_t> +class KDTreeSingleIndexAdaptor + : public KDTreeBaseClass< + KDTreeSingleIndexAdaptor, + Distance, DatasetAdaptor, DIM, IndexType> +{ + public: + /** Deleted copy constructor*/ + explicit KDTreeSingleIndexAdaptor( + const KDTreeSingleIndexAdaptor< + Distance, DatasetAdaptor, DIM, IndexType>&) = delete; + + /** The data source used by this index */ + const DatasetAdaptor& dataset_; + + const KDTreeSingleIndexAdaptorParams indexParams; + + Distance distance_; + + using Base = typename nanoflann::KDTreeBaseClass< + nanoflann::KDTreeSingleIndexAdaptor< + Distance, DatasetAdaptor, DIM, IndexType>, + Distance, DatasetAdaptor, DIM, IndexType>; + + using Offset = typename Base::Offset; + using Size = typename Base::Size; + using Dimension = typename Base::Dimension; + + using ElementType = typename Base::ElementType; + using DistanceType = typename Base::DistanceType; + + using Node = typename Base::Node; + using NodePtr = Node*; + + using Interval = typename Base::Interval; + + /** Define "BoundingBox" as a fixed-size or variable-size container + * depending on "DIM" */ + using BoundingBox = typename Base::BoundingBox; + + /** Define "distance_vector_t" as a fixed-size or variable-size container + * depending on "DIM" */ + using distance_vector_t = typename Base::distance_vector_t; + + /** + * KDTree constructor + * + * Refer to docs in README.md or online in + * https://github.com/jlblancoc/nanoflann + * + * The KD-Tree point dimension (the length of each point in the datase, e.g. + * 3 for 3D points) is determined by means of: + * - The \a DIM template parameter if >0 (highest priority) + * - Otherwise, the \a dimensionality parameter of this constructor. + * + * @param inputData Dataset with the input features. Its lifetime must be + * equal or longer than that of the instance of this class. + * @param params Basically, the maximum leaf node size + * + * Note that there is a variable number of optional additional parameters + * which will be forwarded to the metric class constructor. Refer to example + * `examples/pointcloud_custom_metric.cpp` for a use case. + * + */ + template + explicit KDTreeSingleIndexAdaptor( + const Dimension dimensionality, const DatasetAdaptor& inputData, + const KDTreeSingleIndexAdaptorParams& params, Args&&... args) + : dataset_(inputData), + indexParams(params), + distance_(inputData, std::forward(args)...) + { + init(dimensionality, params); + } + + explicit KDTreeSingleIndexAdaptor( + const Dimension dimensionality, const DatasetAdaptor& inputData, + const KDTreeSingleIndexAdaptorParams& params = {}) + : dataset_(inputData), indexParams(params), distance_(inputData) + { + init(dimensionality, params); + } + + private: + void init( + const Dimension dimensionality, + const KDTreeSingleIndexAdaptorParams& params) + { + Base::size_ = dataset_.kdtree_get_point_count(); + Base::size_at_index_build_ = Base::size_; + Base::dim_ = dimensionality; + if (DIM > 0) Base::dim_ = DIM; + Base::leaf_max_size_ = params.leaf_max_size; + if (params.n_thread_build > 0) + { + Base::n_thread_build_ = params.n_thread_build; + } + else + { + Base::n_thread_build_ = + std::max(std::thread::hardware_concurrency(), 1u); + } + + if (!(params.flags & + KDTreeSingleIndexAdaptorFlags::SkipInitialBuildIndex)) + { + // Build KD-tree: + buildIndex(); + } + } + + public: + /** + * Builds the index + */ + void buildIndex() + { + Base::size_ = dataset_.kdtree_get_point_count(); + Base::size_at_index_build_ = Base::size_; + init_vind(); + this->freeIndex(*this); + Base::size_at_index_build_ = Base::size_; + if (Base::size_ == 0) return; + computeBoundingBox(Base::root_bbox_); + // construct the tree + if (Base::n_thread_build_ == 1) + { + Base::root_node_ = + this->divideTree(*this, 0, Base::size_, Base::root_bbox_); + } + else + { + std::atomic thread_count(0u); + std::mutex mutex; + Base::root_node_ = this->divideTreeConcurrent( + *this, 0, Base::size_, Base::root_bbox_, thread_count, mutex); + } + } + + /** \name Query methods + * @{ */ + + /** + * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored + * inside the result object. + * + * Params: + * result = the result object in which the indices of the + * nearest-neighbors are stored vec = the vector for which to search the + * nearest neighbors + * + * \tparam RESULTSET Should be any ResultSet + * \return True if the requested neighbors could be found. + * \sa knnSearch, radiusSearch + * + * \note If L2 norms are used, all returned distances are actually squared + * distances. + */ + template + bool findNeighbors( + RESULTSET& result, const ElementType* vec, + const SearchParameters& searchParams = {}) const + { + assert(vec); + if (this->size(*this) == 0) return false; + if (!Base::root_node_) { +#if !NANOFLANN_NO_EXCEPTIONS + throw std::runtime_error( + "[nanoflann] findNeighbors() called before building the " + "index."); +#endif + } + float epsError = 1 + searchParams.eps; + + // fixed or variable-sized container (depending on DIM) + distance_vector_t dists; + // Fill it with zeros. + auto zero = static_cast(0); + assign(dists, (DIM > 0 ? DIM : Base::dim_), zero); + DistanceType dist = this->computeInitialDistances(*this, vec, dists); + searchLevel(result, vec, Base::root_node_, dist, dists, epsError); + return result.full(); + } + + /** + * Find the "num_closest" nearest neighbors to the \a query_point[0:dim-1]. + * Their indices and distances are stored in the provided pointers to + * array/vector. + * + * \sa radiusSearch, findNeighbors + * \return Number `N` of valid points in the result set. + * + * \note If L2 norms are used, all returned distances are actually squared + * distances. + * + * \note Only the first `N` entries in `out_indices` and `out_distances` + * will be valid. Return is less than `num_closest` only if the + * number of elements in the tree is less than `num_closest`. + */ + Size knnSearch( + const ElementType* query_point, const Size num_closest, + IndexType* out_indices, DistanceType* out_distances) const + { + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(out_indices, out_distances); + findNeighbors(resultSet, query_point); + return resultSet.size(); + } + + /** + * Find all the neighbors to \a query_point[0:dim-1] within a maximum + * radius. The output is given as a vector of pairs, of which the first + * element is a point index and the second the corresponding distance. + * Previous contents of \a IndicesDists are cleared. + * + * If searchParams.sorted==true, the output list is sorted by ascending + * distances. + * + * For a better performance, it is advisable to do a .reserve() on the + * vector if you have any wild guess about the number of expected matches. + * + * \sa knnSearch, findNeighbors, radiusSearchCustomCallback + * \return The number of points within the given radius (i.e. indices.size() + * or dists.size() ) + * + * \note If L2 norms are used, search radius and all returned distances + * are actually squared distances. + */ + Size radiusSearch( + const ElementType* query_point, const DistanceType& radius, + std::vector>& IndicesDists, + const SearchParameters& searchParams = {}) const + { + RadiusResultSet resultSet( + radius, IndicesDists); + const Size nFound = + radiusSearchCustomCallback(query_point, resultSet, searchParams); + if (searchParams.sorted) + std::sort( + IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter()); + return nFound; + } + + /** + * Just like radiusSearch() but with a custom callback class for each point + * found in the radius of the query. See the source of RadiusResultSet<> as + * a start point for your own classes. \sa radiusSearch + */ + template + Size radiusSearchCustomCallback( + const ElementType* query_point, SEARCH_CALLBACK& resultSet, + const SearchParameters& searchParams = {}) const + { + findNeighbors(resultSet, query_point, searchParams); + return resultSet.size(); + } + + /** + * Find the first N neighbors to \a query_point[0:dim-1] within a maximum + * radius. The output is given as a vector of pairs, of which the first + * element is a point index and the second the corresponding distance. + * Previous contents of \a IndicesDists are cleared. + * + * \sa radiusSearch, findNeighbors + * \return Number `N` of valid points in the result set. + * + * \note If L2 norms are used, all returned distances are actually squared + * distances. + * + * \note Only the first `N` entries in `out_indices` and `out_distances` + * will be valid. Return is less than `num_closest` only if the + * number of elements in the tree is less than `num_closest`. + */ + Size rknnSearch( + const ElementType* query_point, const Size num_closest, + IndexType* out_indices, DistanceType* out_distances, + const DistanceType& radius) const + { + nanoflann::RKNNResultSet resultSet( + num_closest, radius); + resultSet.init(out_indices, out_distances); + findNeighbors(resultSet, query_point); + return resultSet.size(); + } + + /** @} */ + + public: + /** Make sure the auxiliary list \a vind has the same size than the current + * dataset, and re-generate if size has changed. */ + void init_vind() + { + // Create a permutable array of indices to the input vectors. + Base::size_ = dataset_.kdtree_get_point_count(); + if (Base::vAcc_.size() != Base::size_) Base::vAcc_.resize(Base::size_); + for (Size i = 0; i < Base::size_; i++) Base::vAcc_[i] = i; + } + + void computeBoundingBox(BoundingBox& bbox) + { + const auto dims = (DIM > 0 ? DIM : Base::dim_); + resize(bbox, dims); + if (dataset_.kdtree_get_bbox(bbox)) + { + // Done! It was implemented in derived class + } + else + { + const Size N = dataset_.kdtree_get_point_count(); + if (!N) { +#if !NANOFLANN_NO_EXCEPTIONS + throw std::runtime_error( + "[nanoflann] computeBoundingBox() called but " + "no data points found."); +#endif + } + for (Dimension i = 0; i < dims; ++i) + { + bbox[i].low = bbox[i].high = + this->dataset_get(*this, Base::vAcc_[0], i); + } + for (Offset k = 1; k < N; ++k) + { + for (Dimension i = 0; i < dims; ++i) + { + const auto val = + this->dataset_get(*this, Base::vAcc_[k], i); + if (val < bbox[i].low) bbox[i].low = val; + if (val > bbox[i].high) bbox[i].high = val; + } + } + } + } + + /** + * Performs an exact search in the tree starting from a node. + * \tparam RESULTSET Should be any ResultSet + * \return true if the search should be continued, false if the results are + * sufficient + */ + template + bool searchLevel( + RESULTSET& result_set, const ElementType* vec, const NodePtr node, + DistanceType mindist, distance_vector_t& dists, + const float epsError) const + { + /* If this is a leaf node, then do check and return. */ + if ((node->child1 == nullptr) && (node->child2 == nullptr)) + { + DistanceType worst_dist = result_set.worstDist(); + for (Offset i = node->node_type.lr.left; + i < node->node_type.lr.right; ++i) + { + const IndexType accessor = Base::vAcc_[i]; // reorder... : i; + DistanceType dist = distance_.evalMetric( + vec, accessor, (DIM > 0 ? DIM : Base::dim_)); + if (dist < worst_dist) + { + if (!result_set.addPoint(dist, Base::vAcc_[i])) + { + // the resultset doesn't want to receive any more + // points, we're done searching! + return false; + } + } + } + return true; + } + + /* Which child branch should be taken first? */ + Dimension idx = node->node_type.sub.divfeat; + ElementType val = vec[idx]; + DistanceType diff1 = val - node->node_type.sub.divlow; + DistanceType diff2 = val - node->node_type.sub.divhigh; + + NodePtr bestChild; + NodePtr otherChild; + DistanceType cut_dist; + if ((diff1 + diff2) < 0) + { + bestChild = node->child1; + otherChild = node->child2; + cut_dist = + distance_.accum_dist(val, node->node_type.sub.divhigh, idx); + } + else + { + bestChild = node->child2; + otherChild = node->child1; + cut_dist = + distance_.accum_dist(val, node->node_type.sub.divlow, idx); + } + + /* Call recursively to search next level down. */ + if (!searchLevel(result_set, vec, bestChild, mindist, dists, epsError)) + { + // the resultset doesn't want to receive any more points, we're done + // searching! + return false; + } + + DistanceType dst = dists[idx]; + mindist = mindist + cut_dist - dst; + dists[idx] = cut_dist; + if (mindist * epsError <= result_set.worstDist()) + { + if (!searchLevel( + result_set, vec, otherChild, mindist, dists, epsError)) + { + // the resultset doesn't want to receive any more points, we're + // done searching! + return false; + } + } + dists[idx] = dst; + return true; + } + + public: + /** Stores the index in a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so + * when loading the index object it must be constructed associated to the + * same source of data points used while building it. See the example: + * examples/saveload_example.cpp \sa loadIndex */ + void saveIndex(std::ostream& stream) const + { + Base::saveIndex(*this, stream); + } + + /** Loads a previous index from a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so + * the index object must be constructed associated to the same source of + * data points used while building the index. See the example: + * examples/saveload_example.cpp \sa loadIndex */ + void loadIndex(std::istream& stream) { Base::loadIndex(*this, stream); } + +}; // class KDTree + +/** kd-tree dynamic index + * + * Contains the k-d trees and other information for indexing a set of points + * for nearest-neighbor matching. + * + * The class "DatasetAdaptor" must provide the following interface (can be + * non-virtual, inlined methods): + * + * \code + * // Must return the number of data poins + * size_t kdtree_get_point_count() const { ... } + * + * // Must return the dim'th component of the idx'th point in the class: + * T kdtree_get_pt(const size_t idx, const size_t dim) const { ... } + * + * // Optional bounding-box computation: return false to default to a standard + * bbox computation loop. + * // Return true if the BBOX was already computed by the class and returned + * in "bb" so it can be avoided to redo it again. + * // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 + * for point clouds) template bool kdtree_get_bbox(BBOX &bb) const + * { + * bb[0].low = ...; bb[0].high = ...; // 0th dimension limits + * bb[1].low = ...; bb[1].high = ...; // 1st dimension limits + * ... + * return true; + * } + * + * \endcode + * + * \tparam DatasetAdaptor The user-provided adaptor (see comments above). + * \tparam Distance The distance metric to use: nanoflann::metric_L1, + * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. + * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points) + * \tparam IndexType Type of the arguments with which the data can be + * accessed (e.g. float, double, int64_t, T*) + */ +template < + typename Distance, class DatasetAdaptor, int32_t DIM = -1, + typename IndexType = uint32_t> +class KDTreeSingleIndexDynamicAdaptor_ + : public KDTreeBaseClass< + KDTreeSingleIndexDynamicAdaptor_< + Distance, DatasetAdaptor, DIM, IndexType>, + Distance, DatasetAdaptor, DIM, IndexType> +{ + public: + /** + * The dataset used by this index + */ + const DatasetAdaptor& dataset_; //!< The source of our data + + KDTreeSingleIndexAdaptorParams index_params_; + + std::vector& treeIndex_; + + Distance distance_; + + using Base = typename nanoflann::KDTreeBaseClass< + nanoflann::KDTreeSingleIndexDynamicAdaptor_< + Distance, DatasetAdaptor, DIM, IndexType>, + Distance, DatasetAdaptor, DIM, IndexType>; + + using ElementType = typename Base::ElementType; + using DistanceType = typename Base::DistanceType; + + using Offset = typename Base::Offset; + using Size = typename Base::Size; + using Dimension = typename Base::Dimension; + + using Node = typename Base::Node; + using NodePtr = Node*; + + using Interval = typename Base::Interval; + /** Define "BoundingBox" as a fixed-size or variable-size container + * depending on "DIM" */ + using BoundingBox = typename Base::BoundingBox; + + /** Define "distance_vector_t" as a fixed-size or variable-size container + * depending on "DIM" */ + using distance_vector_t = typename Base::distance_vector_t; + + /** + * KDTree constructor + * + * Refer to docs in README.md or online in + * https://github.com/jlblancoc/nanoflann + * + * The KD-Tree point dimension (the length of each point in the datase, e.g. + * 3 for 3D points) is determined by means of: + * - The \a DIM template parameter if >0 (highest priority) + * - Otherwise, the \a dimensionality parameter of this constructor. + * + * @param inputData Dataset with the input features. Its lifetime must be + * equal or longer than that of the instance of this class. + * @param params Basically, the maximum leaf node size + */ + KDTreeSingleIndexDynamicAdaptor_( + const Dimension dimensionality, const DatasetAdaptor& inputData, + std::vector& treeIndex, + const KDTreeSingleIndexAdaptorParams& params = + KDTreeSingleIndexAdaptorParams()) + : dataset_(inputData), + index_params_(params), + treeIndex_(treeIndex), + distance_(inputData) + { + Base::size_ = 0; + Base::size_at_index_build_ = 0; + for (auto& v : Base::root_bbox_) v = {}; + Base::dim_ = dimensionality; + if (DIM > 0) Base::dim_ = DIM; + Base::leaf_max_size_ = params.leaf_max_size; + if (params.n_thread_build > 0) + { + Base::n_thread_build_ = params.n_thread_build; + } + else + { + Base::n_thread_build_ = + std::max(std::thread::hardware_concurrency(), 1u); + } + } + + /** Explicitly default the copy constructor */ + KDTreeSingleIndexDynamicAdaptor_( + const KDTreeSingleIndexDynamicAdaptor_& rhs) = default; + + /** Assignment operator definiton */ + KDTreeSingleIndexDynamicAdaptor_ operator=( + const KDTreeSingleIndexDynamicAdaptor_& rhs) + { + KDTreeSingleIndexDynamicAdaptor_ tmp(rhs); + std::swap(Base::vAcc_, tmp.Base::vAcc_); + std::swap(Base::leaf_max_size_, tmp.Base::leaf_max_size_); + std::swap(index_params_, tmp.index_params_); + std::swap(treeIndex_, tmp.treeIndex_); + std::swap(Base::size_, tmp.Base::size_); + std::swap(Base::size_at_index_build_, tmp.Base::size_at_index_build_); + std::swap(Base::root_node_, tmp.Base::root_node_); + std::swap(Base::root_bbox_, tmp.Base::root_bbox_); + std::swap(Base::pool_, tmp.Base::pool_); + return *this; + } + + /** + * Builds the index + */ + void buildIndex() + { + Base::size_ = Base::vAcc_.size(); + this->freeIndex(*this); + Base::size_at_index_build_ = Base::size_; + if (Base::size_ == 0) return; + computeBoundingBox(Base::root_bbox_); + // construct the tree + if (Base::n_thread_build_ == 1) + { + Base::root_node_ = + this->divideTree(*this, 0, Base::size_, Base::root_bbox_); + } + else + { + std::atomic thread_count(0u); + std::mutex mutex; + Base::root_node_ = this->divideTreeConcurrent( + *this, 0, Base::size_, Base::root_bbox_, thread_count, mutex); + } + } + + /** \name Query methods + * @{ */ + + /** + * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored + * inside the result object. + * This is the core search function, all others are wrappers around this + * one. + * + * \param result The result object in which the indices of the + * nearest-neighbors are stored. + * \param vec The vector of the query point for which to search the + * nearest neighbors. + * \param searchParams Optional parameters for the search. + * + * \tparam RESULTSET Should be any ResultSet + * \return True if the requested neighbors could be found. + * + * \sa knnSearch(), radiusSearch(), radiusSearchCustomCallback() + * + * \note If L2 norms are used, all returned distances are actually squared + * distances. + */ + template + bool findNeighbors( + RESULTSET& result, const ElementType* vec, + const SearchParameters& searchParams = {}) const + { + assert(vec); + if (this->size(*this) == 0) return false; + if (!Base::root_node_) return false; + float epsError = 1 + searchParams.eps; + + // fixed or variable-sized container (depending on DIM) + distance_vector_t dists; + // Fill it with zeros. + assign( + dists, (DIM > 0 ? DIM : Base::dim_), + static_cast(0)); + DistanceType dist = this->computeInitialDistances(*this, vec, dists); + searchLevel(result, vec, Base::root_node_, dist, dists, epsError); + return result.full(); + } + + /** + * Find the "num_closest" nearest neighbors to the \a query_point[0:dim-1]. + * Their indices are stored inside the result object. \sa radiusSearch, + * findNeighbors + * \return Number `N` of valid points in + * the result set. + * + * \note If L2 norms are used, all returned distances are actually squared + * distances. + * + * \note Only the first `N` entries in `out_indices` and `out_distances` + * will be valid. Return may be less than `num_closest` only if the + * number of elements in the tree is less than `num_closest`. + */ + Size knnSearch( + const ElementType* query_point, const Size num_closest, + IndexType* out_indices, DistanceType* out_distances, + const SearchParameters& searchParams = {}) const + { + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(out_indices, out_distances); + findNeighbors(resultSet, query_point, searchParams); + return resultSet.size(); + } + + /** + * Find all the neighbors to \a query_point[0:dim-1] within a maximum + * radius. The output is given as a vector of pairs, of which the first + * element is a point index and the second the corresponding distance. + * Previous contents of \a IndicesDists are cleared. + * + * If searchParams.sorted==true, the output list is sorted by ascending + * distances. + * + * For a better performance, it is advisable to do a .reserve() on the + * vector if you have any wild guess about the number of expected matches. + * + * \sa knnSearch, findNeighbors, radiusSearchCustomCallback + * \return The number of points within the given radius (i.e. indices.size() + * or dists.size() ) + * + * \note If L2 norms are used, search radius and all returned distances + * are actually squared distances. + */ + Size radiusSearch( + const ElementType* query_point, const DistanceType& radius, + std::vector>& IndicesDists, + const SearchParameters& searchParams = {}) const + { + RadiusResultSet resultSet( + radius, IndicesDists); + const size_t nFound = + radiusSearchCustomCallback(query_point, resultSet, searchParams); + if (searchParams.sorted) + std::sort( + IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter()); + return nFound; + } + + /** + * Just like radiusSearch() but with a custom callback class for each point + * found in the radius of the query. See the source of RadiusResultSet<> as + * a start point for your own classes. \sa radiusSearch + */ + template + Size radiusSearchCustomCallback( + const ElementType* query_point, SEARCH_CALLBACK& resultSet, + const SearchParameters& searchParams = {}) const + { + findNeighbors(resultSet, query_point, searchParams); + return resultSet.size(); + } + + /** @} */ + + public: + void computeBoundingBox(BoundingBox& bbox) + { + const auto dims = (DIM > 0 ? DIM : Base::dim_); + resize(bbox, dims); + + if (dataset_.kdtree_get_bbox(bbox)) + { + // Done! It was implemented in derived class + } + else + { + const Size N = Base::size_; + if (!N) { +#if !NANOFLANN_NO_EXCEPTIONS + throw std::runtime_error( + "[nanoflann] computeBoundingBox() called but " + "no data points found."); +#endif + } + for (Dimension i = 0; i < dims; ++i) + { + bbox[i].low = bbox[i].high = + this->dataset_get(*this, Base::vAcc_[0], i); + } + for (Offset k = 1; k < N; ++k) + { + for (Dimension i = 0; i < dims; ++i) + { + const auto val = + this->dataset_get(*this, Base::vAcc_[k], i); + if (val < bbox[i].low) bbox[i].low = val; + if (val > bbox[i].high) bbox[i].high = val; + } + } + } + } + + /** + * Performs an exact search in the tree starting from a node. + * \tparam RESULTSET Should be any ResultSet + */ + template + void searchLevel( + RESULTSET& result_set, const ElementType* vec, const NodePtr node, + DistanceType mindist, distance_vector_t& dists, + const float epsError) const + { + /* If this is a leaf node, then do check and return. */ + if ((node->child1 == nullptr) && (node->child2 == nullptr)) + { + DistanceType worst_dist = result_set.worstDist(); + for (Offset i = node->node_type.lr.left; + i < node->node_type.lr.right; ++i) + { + const IndexType index = Base::vAcc_[i]; // reorder... : i; + if (treeIndex_[index] == -1) continue; + DistanceType dist = distance_.evalMetric( + vec, index, (DIM > 0 ? DIM : Base::dim_)); + if (dist < worst_dist) + { + if (!result_set.addPoint( + static_cast(dist), + static_cast( + Base::vAcc_[i]))) + { + // the resultset doesn't want to receive any more + // points, we're done searching! + return; // false; + } + } + } + return; + } + + /* Which child branch should be taken first? */ + Dimension idx = node->node_type.sub.divfeat; + ElementType val = vec[idx]; + DistanceType diff1 = val - node->node_type.sub.divlow; + DistanceType diff2 = val - node->node_type.sub.divhigh; + + NodePtr bestChild; + NodePtr otherChild; + DistanceType cut_dist; + if ((diff1 + diff2) < 0) + { + bestChild = node->child1; + otherChild = node->child2; + cut_dist = + distance_.accum_dist(val, node->node_type.sub.divhigh, idx); + } + else + { + bestChild = node->child2; + otherChild = node->child1; + cut_dist = + distance_.accum_dist(val, node->node_type.sub.divlow, idx); + } + + /* Call recursively to search next level down. */ + searchLevel(result_set, vec, bestChild, mindist, dists, epsError); + + DistanceType dst = dists[idx]; + mindist = mindist + cut_dist - dst; + dists[idx] = cut_dist; + if (mindist * epsError <= result_set.worstDist()) + { + searchLevel(result_set, vec, otherChild, mindist, dists, epsError); + } + dists[idx] = dst; + } + + public: + /** Stores the index in a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so + * when loading the index object it must be constructed associated to the + * same source of data points used while building it. See the example: + * examples/saveload_example.cpp \sa loadIndex */ + void saveIndex(std::ostream& stream) { saveIndex(*this, stream); } + + /** Loads a previous index from a binary file. + * IMPORTANT NOTE: The set of data points is NOT stored in the file, so + * the index object must be constructed associated to the same source of + * data points used while building the index. See the example: + * examples/saveload_example.cpp \sa loadIndex */ + void loadIndex(std::istream& stream) { loadIndex(*this, stream); } +}; + +/** kd-tree dynaimic index + * + * class to create multiple static index and merge their results to behave as + * single dynamic index as proposed in Logarithmic Approach. + * + * Example of usage: + * examples/dynamic_pointcloud_example.cpp + * + * \tparam DatasetAdaptor The user-provided adaptor (see comments above). + * \tparam Distance The distance metric to use: nanoflann::metric_L1, + * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. \tparam DIM + * Dimensionality of data points (e.g. 3 for 3D points) \tparam IndexType + * Will be typically size_t or int + */ +template < + typename Distance, class DatasetAdaptor, int32_t DIM = -1, + typename IndexType = uint32_t> +class KDTreeSingleIndexDynamicAdaptor +{ + public: + using ElementType = typename Distance::ElementType; + using DistanceType = typename Distance::DistanceType; + + using Offset = typename KDTreeSingleIndexDynamicAdaptor_< + Distance, DatasetAdaptor, DIM>::Offset; + using Size = typename KDTreeSingleIndexDynamicAdaptor_< + Distance, DatasetAdaptor, DIM>::Size; + using Dimension = typename KDTreeSingleIndexDynamicAdaptor_< + Distance, DatasetAdaptor, DIM>::Dimension; + + protected: + Size leaf_max_size_; + Size treeCount_; + Size pointCount_; + + /** + * The dataset used by this index + */ + const DatasetAdaptor& dataset_; //!< The source of our data + + /** treeIndex[idx] is the index of tree in which point at idx is stored. + * treeIndex[idx]=-1 means that point has been removed. */ + std::vector treeIndex_; + std::unordered_set removedPoints_; + + KDTreeSingleIndexAdaptorParams index_params_; + + Dimension dim_; //!< Dimensionality of each data point + + using index_container_t = KDTreeSingleIndexDynamicAdaptor_< + Distance, DatasetAdaptor, DIM, IndexType>; + std::vector index_; + + public: + /** Get a const ref to the internal list of indices; the number of indices + * is adapted dynamically as the dataset grows in size. */ + const std::vector& getAllIndices() const + { + return index_; + } + + private: + /** finds position of least significant unset bit */ + int First0Bit(IndexType num) + { + int pos = 0; + while (num & 1) + { + num = num >> 1; + pos++; + } + return pos; + } + + /** Creates multiple empty trees to handle dynamic support */ + void init() + { + using my_kd_tree_t = KDTreeSingleIndexDynamicAdaptor_< + Distance, DatasetAdaptor, DIM, IndexType>; + std::vector index( + treeCount_, + my_kd_tree_t(dim_ /*dim*/, dataset_, treeIndex_, index_params_)); + index_ = index; + } + + public: + Distance distance_; + + /** + * KDTree constructor + * + * Refer to docs in README.md or online in + * https://github.com/jlblancoc/nanoflann + * + * The KD-Tree point dimension (the length of each point in the datase, e.g. + * 3 for 3D points) is determined by means of: + * - The \a DIM template parameter if >0 (highest priority) + * - Otherwise, the \a dimensionality parameter of this constructor. + * + * @param inputData Dataset with the input features. Its lifetime must be + * equal or longer than that of the instance of this class. + * @param params Basically, the maximum leaf node size + */ + explicit KDTreeSingleIndexDynamicAdaptor( + const int dimensionality, const DatasetAdaptor& inputData, + const KDTreeSingleIndexAdaptorParams& params = + KDTreeSingleIndexAdaptorParams(), + const size_t maximumPointCount = 1000000000U) + : dataset_(inputData), index_params_(params), distance_(inputData) + { + treeCount_ = static_cast(std::log2(maximumPointCount)) + 1; + pointCount_ = 0U; + dim_ = dimensionality; + treeIndex_.clear(); + if (DIM > 0) dim_ = DIM; + leaf_max_size_ = params.leaf_max_size; + init(); + const size_t num_initial_points = dataset_.kdtree_get_point_count(); + if (num_initial_points > 0) { addPoints(0, num_initial_points - 1); } + } + + /** Deleted copy constructor*/ + explicit KDTreeSingleIndexDynamicAdaptor( + const KDTreeSingleIndexDynamicAdaptor< + Distance, DatasetAdaptor, DIM, IndexType>&) = delete; + + /** Add points to the set, Inserts all points from [start, end] */ + void addPoints(IndexType start, IndexType end) + { + const Size count = end - start + 1; + int maxIndex = 0; + treeIndex_.resize(treeIndex_.size() + count); + for (IndexType idx = start; idx <= end; idx++) + { + const int pos = First0Bit(pointCount_); + maxIndex = std::max(pos, maxIndex); + treeIndex_[pointCount_] = pos; + + const auto it = removedPoints_.find(idx); + if (it != removedPoints_.end()) + { + removedPoints_.erase(it); + treeIndex_[idx] = pos; + } + + for (int i = 0; i < pos; i++) + { + for (int j = 0; j < static_cast(index_[i].vAcc_.size()); + j++) + { + index_[pos].vAcc_.push_back(index_[i].vAcc_[j]); + if (treeIndex_[index_[i].vAcc_[j]] != -1) + treeIndex_[index_[i].vAcc_[j]] = pos; + } + index_[i].vAcc_.clear(); + } + index_[pos].vAcc_.push_back(idx); + pointCount_++; + } + + for (int i = 0; i <= maxIndex; ++i) + { + index_[i].freeIndex(index_[i]); + if (!index_[i].vAcc_.empty()) index_[i].buildIndex(); + } + } + + /** Remove a point from the set (Lazy Deletion) */ + void removePoint(size_t idx) + { + if (idx >= pointCount_) return; + removedPoints_.insert(idx); + treeIndex_[idx] = -1; + } + + /** + * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored + * inside the result object. + * + * Params: + * result = the result object in which the indices of the + * nearest-neighbors are stored vec = the vector for which to search the + * nearest neighbors + * + * \tparam RESULTSET Should be any ResultSet + * \return True if the requested neighbors could be found. + * \sa knnSearch, radiusSearch + * + * \note If L2 norms are used, all returned distances are actually squared + * distances. + */ + template + bool findNeighbors( + RESULTSET& result, const ElementType* vec, + const SearchParameters& searchParams = {}) const + { + for (size_t i = 0; i < treeCount_; i++) + { + index_[i].findNeighbors(result, &vec[0], searchParams); + } + return result.full(); + } +}; + +/** An L2-metric KD-tree adaptor for working with data directly stored in an + * Eigen Matrix, without duplicating the data storage. You can select whether a + * row or column in the matrix represents a point in the state space. + * + * Example of usage: + * \code + * Eigen::Matrix mat; + * + * // Fill out "mat"... + * using my_kd_tree_t = nanoflann::KDTreeEigenMatrixAdaptor< + * Eigen::Matrix>; + * + * const int max_leaf = 10; + * my_kd_tree_t mat_index(mat, max_leaf); + * mat_index.index->... + * \endcode + * + * \tparam DIM If set to >0, it specifies a compile-time fixed dimensionality + * for the points in the data set, allowing more compiler optimizations. + * \tparam Distance The distance metric to use: nanoflann::metric_L1, + * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. + * \tparam row_major If set to true the rows of the matrix are used as the + * points, if set to false the columns of the matrix are used as the + * points. + */ +template < + class MatrixType, int32_t DIM = -1, class Distance = nanoflann::metric_L2, + bool row_major = true> +struct KDTreeEigenMatrixAdaptor +{ + using self_t = + KDTreeEigenMatrixAdaptor; + using num_t = typename MatrixType::Scalar; + using IndexType = typename MatrixType::Index; + using metric_t = typename Distance::template traits< + num_t, self_t, IndexType>::distance_t; + + using index_t = KDTreeSingleIndexAdaptor< + metric_t, self_t, + row_major ? MatrixType::ColsAtCompileTime + : MatrixType::RowsAtCompileTime, + IndexType>; + + index_t* index_; //! The kd-tree index for the user to call its methods as + //! usual with any other FLANN index. + + using Offset = typename index_t::Offset; + using Size = typename index_t::Size; + using Dimension = typename index_t::Dimension; + + /// Constructor: takes a const ref to the matrix object with the data points + explicit KDTreeEigenMatrixAdaptor( + const Dimension dimensionality, + const std::reference_wrapper& mat, + const int leaf_max_size = 10) + : m_data_matrix(mat) + { + const auto dims = row_major ? mat.get().cols() : mat.get().rows(); + if (static_cast(dims) != dimensionality) { +#if !NANOFLANN_NO_EXCEPTIONS + throw std::runtime_error( + "Error: 'dimensionality' must match column count in data " + "matrix"); +#endif + } + + if (DIM > 0 && static_cast(dims) != DIM) { +#if !NANOFLANN_NO_EXCEPTIONS + throw std::runtime_error( + "Data set dimensionality does not match the 'DIM' template " + "argument"); +#endif + } + index_ = new index_t( + dims, *this /* adaptor */, + nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size)); + } + + public: + /** Deleted copy constructor */ + KDTreeEigenMatrixAdaptor(const self_t&) = delete; + + ~KDTreeEigenMatrixAdaptor() { delete index_; } + + const std::reference_wrapper m_data_matrix; + + /** Query for the \a num_closest closest points to a given point (entered as + * query_point[0:dim-1]). Note that this is a short-cut method for + * index->findNeighbors(). The user can also call index->... methods as + * desired. + * + * \note If L2 norms are used, all returned distances are actually squared + * distances. + */ + void query( + const num_t* query_point, const Size num_closest, + IndexType* out_indices, num_t* out_distances) const + { + nanoflann::KNNResultSet resultSet(num_closest); + resultSet.init(out_indices, out_distances); + index_->findNeighbors(resultSet, query_point); + } + + /** @name Interface expected by KDTreeSingleIndexAdaptor + * @{ */ + + const self_t& derived() const { return *this; } + self_t& derived() { return *this; } + + // Must return the number of data points + Size kdtree_get_point_count() const + { + if (row_major) + return m_data_matrix.get().rows(); + else + return m_data_matrix.get().cols(); + } + + // Returns the dim'th component of the idx'th point in the class: + num_t kdtree_get_pt(const IndexType idx, size_t dim) const + { + if (row_major) + return m_data_matrix.get().coeff(idx, IndexType(dim)); + else + return m_data_matrix.get().coeff(IndexType(dim), idx); + } + + // Optional bounding-box computation: return false to default to a standard + // bbox computation loop. + // Return true if the BBOX was already computed by the class and returned + // in "bb" so it can be avoided to redo it again. Look at bb.size() to + // find out the expected dimensionality (e.g. 2 or 3 for point clouds) + template + bool kdtree_get_bbox(BBOX& /*bb*/) const + { + return false; + } + + /** @} */ + +}; // end of KDTreeEigenMatrixAdaptor +/** @} */ + +/** @} */ // end of grouping +} // namespace nanoflann diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/stb_image.h b/contrib/tinyusdz/tinyusdz_repo/src/external/stb_image.h new file mode 100644 index 000000000..d0aaf2a7a --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/stb_image.h @@ -0,0 +1,8007 @@ +/* stb_image - v2.28 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff + 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes + 2.26 (2020-07-13) many minor fixes + 2.25 (2020-02-02) fix warnings + 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically + 2.23 (2019-08-11) fix clang static analysis warning + 2.22 (2019-03-04) gif fixes, fix warnings + 2.21 (2019-02-25) fix typo in comment + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine Simon Breuss (16-bit PNM) + John-Mark Allen + Carmelo J Fdez-Aguera + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski + Phil Jordan Dave Moore Roy Eltham + Hayaki Saito Nathan Reed Won Chun + Luke Graham Johan Duparc Nick Verigakis the Horde3D community + Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Eugene Golushkov Laurent Gomila Cort Stratton github:snagar + Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex + Cass Everitt Ryamond Barbiero github:grim210 + Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw + Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus + Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo + Julian Raschke Gregory Mullen Christian Floisand github:darealshinji + Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 + Brad Weinberger Matvey Cherevko github:mosra + Luca Sas Alexander Veselov Zack Middleton [reserved] + Ryan C. Gordon [reserved] [reserved] + DO NOT ADD YOUR NAME HERE + + Jacko Dirks + + To add your name to the credits, pick a random blank space in the middle and fill it. + 80% of merge conflicts on stb PRs are due to people adding their name at the end + of the credits. +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data); +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// To query the width, height and component count of an image without having to +// decode the full file, you can use the stbi_info family of functions: +// +// int x,y,n,ok; +// ok = stbi_info(filename, &x, &y, &n); +// // returns ok=1 and sets x, y, n if image is a supported format, +// // 0 otherwise. +// +// Note that stb_image pervasively uses ints in its public API for sizes, +// including sizes of memory buffers. This is now part of the API and thus +// hard to change without causing breakage. As a result, the various image +// loaders all have certain limits on image size; these differ somewhat +// by format but generally boil down to either just under 2GB or just under +// 1GB. When the decoded image would be larger than this, stb_image decoding +// will fail. +// +// Additionally, stb_image will reject image files that have any of their +// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, +// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, +// the only way to have an image with such dimensions load correctly +// is for it to have a rather extreme aspect ratio. Either way, the +// assumption here is that such larger images are likely to be malformed +// or malicious. If you do need to load an image with individual dimensions +// larger than that, and it still fits in the overall size limit, you can +// #define STBI_MAX_DIMENSIONS on your own to be something larger. +// +// =========================================================================== +// +// UNICODE: +// +// If compiling for Windows and you wish to use Unicode filenames, compile +// with +// #define STBI_WINDOWS_UTF8 +// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert +// Windows wchar_t filenames to utf8. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy-to-use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// provide more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image supports loading HDR images in general, and currently the Radiance +// .HDR file format specifically. You can still load any file through the existing +// interface; if you attempt to load an HDR file, it will be automatically remapped +// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// We optionally support converting iPhone-formatted PNGs (which store +// premultiplied BGRA) back to RGB, even though they're internally encoded +// differently. To enable this conversion, call +// stbi_convert_iphone_png_to_rgb(1). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// +// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater +// than that size (in either width or height) without further processing. +// This is to let programs in the wild set an upper bound to prevent +// denial-of-service attacks on untrusted data, as one could generate a +// valid image of gigantic dimensions and force stb_image to allocate a +// huge block of memory and spend disproportionate time decoding it. By +// default this is set to (1 << 24), which is 16777216, but that's still +// very big. + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +#include +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef STBIDEF +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// on most compilers (and ALL modern mainstream compilers) this is threadsafe +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// as above, but only applies to images loaded on the thread that calls the function +// this function is only available if your compiler supports thread-local variables; +// calling it will fail to link if your compiler doesn't +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + +#ifndef STBI_NO_THREAD_LOCALS + #if defined(__cplusplus) && __cplusplus >= 201103L + #define STBI_THREAD_LOCAL thread_local + #elif defined(__GNUC__) && __GNUC__ < 5 + #define STBI_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define STBI_THREAD_LOCAL __declspec(thread) + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define STBI_THREAD_LOCAL _Thread_local + #endif + + #ifndef STBI_THREAD_LOCAL + #if defined(__GNUC__) + #define STBI_THREAD_LOCAL __thread + #endif + #endif +#endif + +#if defined(_MSC_VER) || defined(__SYMBIAN32__) +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#endif + +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif + +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +#ifdef _MSC_VER +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name +#else +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +#ifndef STBI_MAX_DIMENSIONS +#define STBI_MAX_DIMENSIONS (1 << 24) +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + int callback_already_read; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + int ch; + fseek((FILE*) user, n, SEEK_CUR); + ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ + if (ch != EOF) { + ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ + } +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user) || ferror((FILE *) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__pnm_is16(stbi__context *s); +#endif + +static +#ifdef STBI_THREAD_LOCAL +STBI_THREAD_LOCAL +#endif +const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +#ifndef STBI_NO_FAILURE_STRINGS +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} +#endif + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} +#endif + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} +#endif + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow. +static int stbi__addints_valid(int a, int b) +{ + if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow + if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0. + return a <= INT_MAX - b; +} + +// returns 1 if the product of two signed shorts is valid, 0 on overflow. +static int stbi__mul2shorts_valid(short a, short b) +{ + if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow + if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid + if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN + return a >= SHRT_MIN / b; +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load_global = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_global = flag_true_if_should_flip; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global +#else +static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; + +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_local = flag_true_if_should_flip; + stbi__vertically_flip_on_load_set = 1; +} + +#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ + ? stbi__vertically_flip_on_load_local \ + : stbi__vertically_flip_on_load_global) +#endif // STBI_THREAD_LOCAL + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + // test the formats with a very explicit header first (at least a FOURCC + // or distinctive magic number first) + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #else + STBI_NOTUSED(bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + + // then the formats that can end up attempting to load with just 1 or 2 + // bytes matching expectations; these are prone to false positives, so + // try them later + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 8) { + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 16) { + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) +// nothing +#else +static void stbi__skip(stbi__context *s, int n) +{ + if (n == 0) return; // already there! + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) +// nothing +#else +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} +#endif + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + z += (stbi__uint32)stbi__get16le(s) << 16; + return z; +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) { + for (j=0; j < count[i]; ++j) { + h->size[k++] = (stbi_uc) (i+1); + if(k >= 257) return stbi__err("bad size list","Corrupt JPEG"); + } + } + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + if(c < 0 || c >= 256) // symbol id out of bounds! + return -1; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + + sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & (sgn - 1)); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if ((dc > SHRT_MAX) || (dequant[0] > SHRT_MAX) || !stbi__mul2shorts_valid((short) dc, (short) dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + diff = t ? stbi__extend_receive(j, t) : 0; + + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if ((dc > SHRT_MAX) || !stbi__mul2shorts_valid((short) dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * (1 << j->succ_low)); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * (1 << shift)); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values! + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios + // and I've never seen a non-corrupted JPEG file actually use them + for (i=0; i < s->img_n; ++i) { + if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); + if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) +{ + // some JPEGs have junk at end, skip over it but if we find what looks + // like a valid marker, resume there + while (!stbi__at_eof(j->s)) { + stbi_uc x = stbi__get8(j->s); + while (x == 255) { // might be a marker + if (stbi__at_eof(j->s)) return STBI__MARKER_none; + x = stbi__get8(j->s); + if (x != 0x00 && x != 0xff) { + // not a stuffed zero or lead-in to another marker, looks + // like an actual marker, return it + return x; + } + // stuffed zero has x=0 now which ends the loop, meaning we go + // back to regular scan loop. + // repeated 0xff keeps trying to read the next byte of the marker. + } + } + return STBI__MARKER_none; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + j->marker = stbi__skip_jpeg_junk_at_end(j); + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + m = stbi__get_marker(j); + if (STBI__RESTART(m)) + m = stbi__get_marker(j); + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + m = stbi__get_marker(j); + } else { + if (!stbi__process_marker(j, m)) return 1; + m = stbi__get_marker(j); + } + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // nothing to do if no components requested; check this now to avoid + // accessing uninitialized coutput[0] later + if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__errpuc("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) +#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[STBI__ZNSYMS]; + stbi__uint16 value[STBI__ZNSYMS]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + int hit_zeof_once; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static int stbi__zeof(stbi__zbuf *z) +{ + return (z->zbuffer >= z->zbuffer_end); +} + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + return stbi__zeof(z) ? 0 : *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + if (z->code_buffer >= (1U << z->num_bits)) { + z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ + return; + } + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s >= 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! + if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) { + if (stbi__zeof(a)) { + if (!a->hit_zeof_once) { + // This is the first time we hit eof, insert 16 extra padding btis + // to allow us to keep going; if we actually consume any of them + // though, that is invalid data. This is caught later. + a->hit_zeof_once = 1; + a->num_bits += 16; // add 16 implicit zero bits + } else { + // We already inserted our extra 16 padding bits and are again + // out, this stream is actually prematurely terminated. + return -1; + } + } else { + stbi__fill_bits(a); + } + } + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + unsigned int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (unsigned int) (z->zout - z->zout_start); + limit = old_limit = (unsigned) (z->zout_end - z->zout_start); + if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); + while (cur + n > limit) { + if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); + limit *= 2; + } + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + if (a->hit_zeof_once && a->num_bits < 16) { + // The first time we hit zeof, we inserted 16 extra zero bits into our bit + // buffer so the decoder can just do its speculative decoding. But if we + // actually consumed any of those bits (which is the case when num_bits < 16), + // the stream actually read past the end so it is malformed. + return stbi__err("unexpected end","Corrupt PNG"); + } + return 1; + } + if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (len > a->zout_end - zout) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + } else if (c == 18) { + c = stbi__zreceive(a,7)+11; + } else { + return stbi__err("bad codelengths", "Corrupt PNG"); + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + a->hit_zeof_once = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior; + int filter = *raw++; + + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG"); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; // first pixel top byte + cur[filter_bytes+1] = 255; // first pixel bottom byte + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define STBI__CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; + } + #undef STBI__CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define STBI__CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; + } + #undef STBI__CASE + + // the loop above sets the high byte of the pixels' alpha, but for + // 16 bit png files we also need the low byte set. we'll do that here. + if (depth == 16) { + cur = a->out + stride*j; // start at the beginning of the row again + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + // force the image data from big-endian to platform-native. + // this is done in a separate pass due to the decoding relying + // on the data being untouched, but could probably be done + // per-line during decode if care is taken. + stbi_uc *cur = a->out; + stbi__uint16 *cur16 = (stbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + if (!final) return stbi__err("outofmem", "Out of memory"); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load_global = 0; +static int stbi__de_iphone_flag_global = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_global = flag_true_if_should_convert; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global +#define stbi__de_iphone_flag stbi__de_iphone_flag_global +#else +static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; +static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; + +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; + stbi__unpremultiply_on_load_set = 1; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_local = flag_true_if_should_convert; + stbi__de_iphone_flag_set = 1; +} + +#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ + ? stbi__unpremultiply_on_load_local \ + : stbi__unpremultiply_on_load_global) +#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ + ? stbi__de_iphone_flag_local \ + : stbi__de_iphone_flag_global) +#endif // STBI_THREAD_LOCAL + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]={0}; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); + s->img_y = stbi__get32be(s); + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + } + // even with SCAN_header, have to scan to see if we have a tRNS + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now. + if (scan == STBI__SCAN_header) { ++s->img_n; return 1; } + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { + // header scan definitely stops at first IDAT + if (pal_img_n) + s->img_n = pal_img_n; + return 1; + } + if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes"); + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth <= 8) + ri->bits_per_channel = 8; + else if (p->depth == 16) + ri->bits_per_channel = 16; + else + return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1;/* >>= 1;*/ } + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; + int extra_read; +} stbi__bmp_data; + +static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) +{ + // BI_BITFIELDS specifies masks explicitly, don't override + if (compress == 3) + return 1; + + if (compress == 0) { + if (info->bpp == 16) { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } else if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + // otherwise, use defaults, which is all-0 + info->mr = info->mg = info->mb = info->ma = 0; + } + return 1; + } + return 0; // error +} + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + info->extra_read = 14; + + if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes + if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + stbi__bmp_set_mask_defaults(info, compress); + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->extra_read += 12; + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + // V4/V5 header + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs + stbi__bmp_set_mask_defaults(info, compress); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - info.extra_read - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - info.extra_read - info.hsz) >> 2; + } + if (psize == 0) { + // accept some number of extra bytes after the header, but if the offset points either to before + // the header ends or implies a large amount of extra data, reject the file as malformed + int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original); + int header_limit = 1024; // max we actually read is below 256 bytes currently. + int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size. + if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) { + return stbi__errpuc("bad header", "Corrupt BMP"); + } + // we established that bytes_read_so_far is positive and sensible. + // the first half of this test rejects offsets that are either too small positives, or + // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn + // ensures the number computed in the second half of the test can't overflow. + if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } else { + stbi__skip(s, info.offset - bytes_read_so_far); + } + } + + if (info.bpp == 24 && ma == 0xff000000) + s->img_n = 3; + else + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - info.extra_read - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i]; p1[i] = p2[i]; p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + STBI_NOTUSED(tga_x_origin); // @TODO + STBI_NOTUSED(tga_y_origin); // @TODO + + if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + if (tga_palette_len == 0) { /* you have to have at least one entry! */ + STBI_FREE(tga_data); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + STBI_NOTUSED(tga_palette_start); + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + + if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + if (!result) return stbi__errpuc("outofmem", "Out of memory"); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + return 0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!g) return stbi__err("outofmem", "Out of memory"); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + memset(g->history, 0x00, pcount); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispose of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (!o) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) +{ + STBI_FREE(g->out); + STBI_FREE(g->history); + STBI_FREE(g->background); + + if (out) STBI_FREE(out); + if (delays && *delays) STBI_FREE(*delays); + return stbi__errpuc("outofmem", "Out of memory"); +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + int out_size = 0; + int delays_size = 0; + + STBI_NOTUSED(out_size); + STBI_NOTUSED(delays_size); + + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); + if (!tmp) + return stbi__load_gif_main_outofmem(&g, out, delays); + else { + out = (stbi_uc*) tmp; + out_size = layers * stride; + } + + if (delays) { + int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + if (!new_delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + *delays = new_delays; + delays_size = layers * sizeof(int); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (!out) + return stbi__load_gif_main_outofmem(&g, out, delays); + out_size = layers * stride; + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + if (!*delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + delays_size = layers * sizeof(int); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + if (p == NULL) { + stbi__rewind( s ); + return 0; + } + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) { + if (info.bpp == 24 && info.ma == 0xff000000) + *comp = 3; + else + *comp = info.ma ? 4 : 3; + } + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + STBI_NOTUSED(stbi__get32be(s)); + STBI_NOTUSED(stbi__get32be(s)); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); + if (ri->bits_per_channel == 0) + return 0; + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) { + STBI_FREE(out); + return stbi__errpuc("bad PNM", "PNM file truncated"); + } + + if (req_comp && req_comp != s->img_n) { + if (ri->bits_per_channel == 16) { + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y); + } else { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + } + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + if((value > 214748364) || (value == 214748364 && *c > '7')) + return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int"); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + if(*x == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + if (*y == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + if (maxv > 65535) + return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); + else if (maxv > 255) + return 16; + else + return 8; +} + +static int stbi__pnm_is16(stbi__context *s) +{ + if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) + return 1; + return 0; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_is16(s)) return 1; + #endif + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/stb_image_resize.h b/contrib/tinyusdz/tinyusdz_repo/src/external/stb_image_resize.h new file mode 100644 index 000000000..ef9e6fe87 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/stb_image_resize.h @@ -0,0 +1,2634 @@ +/* stb_image_resize - v0.97 - public domain image resizing + by Jorge L Rodriguez (@VinoBS) - 2014 + http://github.com/nothings/stb + + Written with emphasis on usability, portability, and efficiency. (No + SIMD or threads, so it be easily outperformed by libs that use those.) + Only scaling and translation is supported, no rotations or shears. + Easy API downsamples w/Mitchell filter, upsamples w/cubic interpolation. + + COMPILING & LINKING + In one C/C++ file that #includes this file, do this: + #define STB_IMAGE_RESIZE_IMPLEMENTATION + before the #include. That will create the implementation in that file. + + QUICKSTART + stbir_resize_uint8( input_pixels , in_w , in_h , 0, + output_pixels, out_w, out_h, 0, num_channels) + stbir_resize_float(...) + stbir_resize_uint8_srgb( input_pixels , in_w , in_h , 0, + output_pixels, out_w, out_h, 0, + num_channels , alpha_chan , 0) + stbir_resize_uint8_srgb_edgemode( + input_pixels , in_w , in_h , 0, + output_pixels, out_w, out_h, 0, + num_channels , alpha_chan , 0, STBIR_EDGE_CLAMP) + // WRAP/REFLECT/ZERO + + FULL API + See the "header file" section of the source for API documentation. + + ADDITIONAL DOCUMENTATION + + SRGB & FLOATING POINT REPRESENTATION + The sRGB functions presume IEEE floating point. If you do not have + IEEE floating point, define STBIR_NON_IEEE_FLOAT. This will use + a slower implementation. + + MEMORY ALLOCATION + The resize functions here perform a single memory allocation using + malloc. To control the memory allocation, before the #include that + triggers the implementation, do: + + #define STBIR_MALLOC(size,context) ... + #define STBIR_FREE(ptr,context) ... + + Each resize function makes exactly one call to malloc/free, so to use + temp memory, store the temp memory in the context and return that. + + ASSERT + Define STBIR_ASSERT(boolval) to override assert() and not use assert.h + + OPTIMIZATION + Define STBIR_SATURATE_INT to compute clamp values in-range using + integer operations instead of float operations. This may be faster + on some platforms. + + DEFAULT FILTERS + For functions which don't provide explicit control over what filters + to use, you can change the compile-time defaults with + + #define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_something + #define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_something + + See stbir_filter in the header-file section for the list of filters. + + NEW FILTERS + A number of 1D filter kernels are used. For a list of + supported filters see the stbir_filter enum. To add a new filter, + write a filter function and add it to stbir__filter_info_table. + + PROGRESS + For interactive use with slow resize operations, you can install + a progress-report callback: + + #define STBIR_PROGRESS_REPORT(val) some_func(val) + + The parameter val is a float which goes from 0 to 1 as progress is made. + + For example: + + static void my_progress_report(float progress); + #define STBIR_PROGRESS_REPORT(val) my_progress_report(val) + + #define STB_IMAGE_RESIZE_IMPLEMENTATION + #include "stb_image_resize.h" + + static void my_progress_report(float progress) + { + printf("Progress: %f%%\n", progress*100); + } + + MAX CHANNELS + If your image has more than 64 channels, define STBIR_MAX_CHANNELS + to the max you'll have. + + ALPHA CHANNEL + Most of the resizing functions provide the ability to control how + the alpha channel of an image is processed. The important things + to know about this: + + 1. The best mathematically-behaved version of alpha to use is + called "premultiplied alpha", in which the other color channels + have had the alpha value multiplied in. If you use premultiplied + alpha, linear filtering (such as image resampling done by this + library, or performed in texture units on GPUs) does the "right + thing". While premultiplied alpha is standard in the movie CGI + industry, it is still uncommon in the videogame/real-time world. + + If you linearly filter non-premultiplied alpha, strange effects + occur. (For example, the 50/50 average of 99% transparent bright green + and 1% transparent black produces 50% transparent dark green when + non-premultiplied, whereas premultiplied it produces 50% + transparent near-black. The former introduces green energy + that doesn't exist in the source image.) + + 2. Artists should not edit premultiplied-alpha images; artists + want non-premultiplied alpha images. Thus, art tools generally output + non-premultiplied alpha images. + + 3. You will get best results in most cases by converting images + to premultiplied alpha before processing them mathematically. + + 4. If you pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED, the + resizer does not do anything special for the alpha channel; + it is resampled identically to other channels. This produces + the correct results for premultiplied-alpha images, but produces + less-than-ideal results for non-premultiplied-alpha images. + + 5. If you do not pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED, + then the resizer weights the contribution of input pixels + based on their alpha values, or, equivalently, it multiplies + the alpha value into the color channels, resamples, then divides + by the resultant alpha value. Input pixels which have alpha=0 do + not contribute at all to output pixels unless _all_ of the input + pixels affecting that output pixel have alpha=0, in which case + the result for that pixel is the same as it would be without + STBIR_FLAG_ALPHA_PREMULTIPLIED. However, this is only true for + input images in integer formats. For input images in float format, + input pixels with alpha=0 have no effect, and output pixels + which have alpha=0 will be 0 in all channels. (For float images, + you can manually achieve the same result by adding a tiny epsilon + value to the alpha channel of every image, and then subtracting + or clamping it at the end.) + + 6. You can suppress the behavior described in #5 and make + all-0-alpha pixels have 0 in all channels by #defining + STBIR_NO_ALPHA_EPSILON. + + 7. You can separately control whether the alpha channel is + interpreted as linear or affected by the colorspace. By default + it is linear; you almost never want to apply the colorspace. + (For example, graphics hardware does not apply sRGB conversion + to the alpha channel.) + + CONTRIBUTORS + Jorge L Rodriguez: Implementation + Sean Barrett: API design, optimizations + Aras Pranckevicius: bugfix + Nathan Reed: warning fixes + + REVISIONS + 0.97 (2020-02-02) fixed warning + 0.96 (2019-03-04) fixed warnings + 0.95 (2017-07-23) fixed warnings + 0.94 (2017-03-18) fixed warnings + 0.93 (2017-03-03) fixed bug with certain combinations of heights + 0.92 (2017-01-02) fix integer overflow on large (>2GB) images + 0.91 (2016-04-02) fix warnings; fix handling of subpixel regions + 0.90 (2014-09-17) first released version + + LICENSE + See end of file for license information. + + TODO + Don't decode all of the image data when only processing a partial tile + Don't use full-width decode buffers when only processing a partial tile + When processing wide images, break processing into tiles so data fits in L1 cache + Installable filters? + Resize that respects alpha test coverage + (Reference code: FloatImage::alphaTestCoverage and FloatImage::scaleAlphaToCoverage: + https://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvimage/FloatImage.cpp ) +*/ + +#ifndef STBIR_INCLUDE_STB_IMAGE_RESIZE_H +#define STBIR_INCLUDE_STB_IMAGE_RESIZE_H + +#ifdef _MSC_VER +typedef unsigned char stbir_uint8; +typedef unsigned short stbir_uint16; +typedef unsigned int stbir_uint32; +#else +#include +typedef uint8_t stbir_uint8; +typedef uint16_t stbir_uint16; +typedef uint32_t stbir_uint32; +#endif + +#ifndef STBIRDEF +#ifdef STB_IMAGE_RESIZE_STATIC +#define STBIRDEF static +#else +#ifdef __cplusplus +#define STBIRDEF extern "C" +#else +#define STBIRDEF extern +#endif +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Easy-to-use API: +// +// * "input pixels" points to an array of image data with 'num_channels' channels (e.g. RGB=3, RGBA=4) +// * input_w is input image width (x-axis), input_h is input image height (y-axis) +// * stride is the offset between successive rows of image data in memory, in bytes. you can +// specify 0 to mean packed continuously in memory +// * alpha channel is treated identically to other channels. +// * colorspace is linear or sRGB as specified by function name +// * returned result is 1 for success or 0 in case of an error. +// #define STBIR_ASSERT() to trigger an assert on parameter validation errors. +// * Memory required grows approximately linearly with input and output size, but with +// discontinuities at input_w == output_w and input_h == output_h. +// * These functions use a "default" resampling filter defined at compile time. To change the filter, +// you can change the compile-time defaults by #defining STBIR_DEFAULT_FILTER_UPSAMPLE +// and STBIR_DEFAULT_FILTER_DOWNSAMPLE, or you can use the medium-complexity API. + +STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels); + +STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels); + + +// The following functions interpret image data as gamma-corrected sRGB. +// Specify STBIR_ALPHA_CHANNEL_NONE if you have no alpha channel, +// or otherwise provide the index of the alpha channel. Flags value +// of 0 will probably do the right thing if you're not sure what +// the flags mean. + +#define STBIR_ALPHA_CHANNEL_NONE -1 + +// Set this flag if your texture has premultiplied alpha. Otherwise, stbir will +// use alpha-weighted resampling (effectively premultiplying, resampling, +// then unpremultiplying). +#define STBIR_FLAG_ALPHA_PREMULTIPLIED (1 << 0) +// The specified alpha channel should be handled as gamma-corrected value even +// when doing sRGB operations. +#define STBIR_FLAG_ALPHA_USES_COLORSPACE (1 << 1) + +STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags); + + +typedef enum +{ + STBIR_EDGE_CLAMP = 1, + STBIR_EDGE_REFLECT = 2, + STBIR_EDGE_WRAP = 3, + STBIR_EDGE_ZERO = 4, +} stbir_edge; + +// This function adds the ability to specify how requests to sample off the edge of the image are handled. +STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode); + +////////////////////////////////////////////////////////////////////////////// +// +// Medium-complexity API +// +// This extends the easy-to-use API as follows: +// +// * Alpha-channel can be processed separately +// * If alpha_channel is not STBIR_ALPHA_CHANNEL_NONE +// * Alpha channel will not be gamma corrected (unless flags&STBIR_FLAG_GAMMA_CORRECT) +// * Filters will be weighted by alpha channel (unless flags&STBIR_FLAG_ALPHA_PREMULTIPLIED) +// * Filter can be selected explicitly +// * uint16 image type +// * sRGB colorspace available for all types +// * context parameter for passing to STBIR_MALLOC + +typedef enum +{ + STBIR_FILTER_DEFAULT = 0, // use same filter type that easy-to-use API chooses + STBIR_FILTER_BOX = 1, // A trapezoid w/1-pixel wide ramps, same result as box for integer scale ratios + STBIR_FILTER_TRIANGLE = 2, // On upsampling, produces same results as bilinear texture filtering + STBIR_FILTER_CUBICBSPLINE = 3, // The cubic b-spline (aka Mitchell-Netrevalli with B=1,C=0), gaussian-esque + STBIR_FILTER_CATMULLROM = 4, // An interpolating cubic spline + STBIR_FILTER_MITCHELL = 5, // Mitchell-Netrevalli filter with B=1/3, C=1/3 +} stbir_filter; + +typedef enum +{ + STBIR_COLORSPACE_LINEAR, + STBIR_COLORSPACE_SRGB, + + STBIR_MAX_COLORSPACES, +} stbir_colorspace; + +// The following functions are all identical except for the type of the image data + +STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + +STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + +STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context); + + + +////////////////////////////////////////////////////////////////////////////// +// +// Full-complexity API +// +// This extends the medium API as follows: +// +// * uint32 image type +// * not typesafe +// * separate filter types for each axis +// * separate edge modes for each axis +// * can specify scale explicitly for subpixel correctness +// * can specify image source tile using texture coordinates + +typedef enum +{ + STBIR_TYPE_UINT8 , + STBIR_TYPE_UINT16, + STBIR_TYPE_UINT32, + STBIR_TYPE_FLOAT , + + STBIR_MAX_TYPES +} stbir_datatype; + +STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context); + +STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float x_scale, float y_scale, + float x_offset, float y_offset); + +STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float s0, float t0, float s1, float t1); +// (s0, t0) & (s1, t1) are the top-left and bottom right corner (uv addressing style: [0, 1]x[0, 1]) of a region of the input image to use. + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBIR_INCLUDE_STB_IMAGE_RESIZE_H + + + + + +#ifdef STB_IMAGE_RESIZE_IMPLEMENTATION + +#ifndef STBIR_ASSERT +#include +#define STBIR_ASSERT(x) assert(x) +#endif + +// For memset +#include + +#include + +#ifndef STBIR_MALLOC +#include +// use comma operator to evaluate c, to avoid "unused parameter" warnings +#define STBIR_MALLOC(size,c) ((void)(c), malloc(size)) +#define STBIR_FREE(ptr,c) ((void)(c), free(ptr)) +#endif + +#ifndef _MSC_VER +#ifdef __cplusplus +#define stbir__inline inline +#else +#define stbir__inline +#endif +#else +#define stbir__inline __forceinline +#endif + + +// should produce compiler error if size is wrong +typedef unsigned char stbir__validate_uint32[sizeof(stbir_uint32) == 4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBIR__NOTUSED(v) (void)(v) +#else +#define STBIR__NOTUSED(v) (void)sizeof(v) +#endif + +#define STBIR__ARRAY_SIZE(a) (sizeof((a))/sizeof((a)[0])) + +#ifndef STBIR_DEFAULT_FILTER_UPSAMPLE +#define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM +#endif + +#ifndef STBIR_DEFAULT_FILTER_DOWNSAMPLE +#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL +#endif + +#ifndef STBIR_PROGRESS_REPORT +#define STBIR_PROGRESS_REPORT(float_0_to_1) +#endif + +#ifndef STBIR_MAX_CHANNELS +#define STBIR_MAX_CHANNELS 64 +#endif + +#if STBIR_MAX_CHANNELS > 65536 +#error "Too many channels; STBIR_MAX_CHANNELS must be no more than 65536." +// because we store the indices in 16-bit variables +#endif + +// This value is added to alpha just before premultiplication to avoid +// zeroing out color values. It is equivalent to 2^-80. If you don't want +// that behavior (it may interfere if you have floating point images with +// very small alpha values) then you can define STBIR_NO_ALPHA_EPSILON to +// disable it. +#ifndef STBIR_ALPHA_EPSILON +#define STBIR_ALPHA_EPSILON ((float)1 / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20)) +#endif + + + +#ifdef _MSC_VER +#define STBIR__UNUSED_PARAM(v) (void)(v) +#else +#define STBIR__UNUSED_PARAM(v) (void)sizeof(v) +#endif + +// must match stbir_datatype +static unsigned char stbir__type_size[] = { + 1, // STBIR_TYPE_UINT8 + 2, // STBIR_TYPE_UINT16 + 4, // STBIR_TYPE_UINT32 + 4, // STBIR_TYPE_FLOAT +}; + +// Kernel function centered at 0 +typedef float (stbir__kernel_fn)(float x, float scale); +typedef float (stbir__support_fn)(float scale); + +typedef struct +{ + stbir__kernel_fn* kernel; + stbir__support_fn* support; +} stbir__filter_info; + +// When upsampling, the contributors are which source pixels contribute. +// When downsampling, the contributors are which destination pixels are contributed to. +typedef struct +{ + int n0; // First contributing pixel + int n1; // Last contributing pixel +} stbir__contributors; + +typedef struct +{ + const void* input_data; + int input_w; + int input_h; + int input_stride_bytes; + + void* output_data; + int output_w; + int output_h; + int output_stride_bytes; + + float s0, t0, s1, t1; + + float horizontal_shift; // Units: output pixels + float vertical_shift; // Units: output pixels + float horizontal_scale; + float vertical_scale; + + int channels; + int alpha_channel; + stbir_uint32 flags; + stbir_datatype type; + stbir_filter horizontal_filter; + stbir_filter vertical_filter; + stbir_edge edge_horizontal; + stbir_edge edge_vertical; + stbir_colorspace colorspace; + + stbir__contributors* horizontal_contributors; + float* horizontal_coefficients; + + stbir__contributors* vertical_contributors; + float* vertical_coefficients; + + int decode_buffer_pixels; + float* decode_buffer; + + float* horizontal_buffer; + + // cache these because ceil/floor are inexplicably showing up in profile + int horizontal_coefficient_width; + int vertical_coefficient_width; + int horizontal_filter_pixel_width; + int vertical_filter_pixel_width; + int horizontal_filter_pixel_margin; + int vertical_filter_pixel_margin; + int horizontal_num_contributors; + int vertical_num_contributors; + + int ring_buffer_length_bytes; // The length of an individual entry in the ring buffer. The total number of ring buffers is stbir__get_filter_pixel_width(filter) + int ring_buffer_num_entries; // Total number of entries in the ring buffer. + int ring_buffer_first_scanline; + int ring_buffer_last_scanline; + int ring_buffer_begin_index; // first_scanline is at this index in the ring buffer + float* ring_buffer; + + float* encode_buffer; // A temporary buffer to store floats so we don't lose precision while we do multiply-adds. + + int horizontal_contributors_size; + int horizontal_coefficients_size; + int vertical_contributors_size; + int vertical_coefficients_size; + int decode_buffer_size; + int horizontal_buffer_size; + int ring_buffer_size; + int encode_buffer_size; +} stbir__info; + + +static const float stbir__max_uint8_as_float = 255.0f; +static const float stbir__max_uint16_as_float = 65535.0f; +static const double stbir__max_uint32_as_float = 4294967295.0; + + +static stbir__inline int stbir__min(int a, int b) +{ + return a < b ? a : b; +} + +static stbir__inline float stbir__saturate(float x) +{ + if (x < 0) + return 0; + + if (x > 1) + return 1; + + return x; +} + +#ifdef STBIR_SATURATE_INT +static stbir__inline stbir_uint8 stbir__saturate8(int x) +{ + if ((unsigned int) x <= 255) + return x; + + if (x < 0) + return 0; + + return 255; +} + +static stbir__inline stbir_uint16 stbir__saturate16(int x) +{ + if ((unsigned int) x <= 65535) + return x; + + if (x < 0) + return 0; + + return 65535; +} +#endif + +static float stbir__srgb_uchar_to_linear_float[256] = { + 0.000000f, 0.000304f, 0.000607f, 0.000911f, 0.001214f, 0.001518f, 0.001821f, 0.002125f, 0.002428f, 0.002732f, 0.003035f, + 0.003347f, 0.003677f, 0.004025f, 0.004391f, 0.004777f, 0.005182f, 0.005605f, 0.006049f, 0.006512f, 0.006995f, 0.007499f, + 0.008023f, 0.008568f, 0.009134f, 0.009721f, 0.010330f, 0.010960f, 0.011612f, 0.012286f, 0.012983f, 0.013702f, 0.014444f, + 0.015209f, 0.015996f, 0.016807f, 0.017642f, 0.018500f, 0.019382f, 0.020289f, 0.021219f, 0.022174f, 0.023153f, 0.024158f, + 0.025187f, 0.026241f, 0.027321f, 0.028426f, 0.029557f, 0.030713f, 0.031896f, 0.033105f, 0.034340f, 0.035601f, 0.036889f, + 0.038204f, 0.039546f, 0.040915f, 0.042311f, 0.043735f, 0.045186f, 0.046665f, 0.048172f, 0.049707f, 0.051269f, 0.052861f, + 0.054480f, 0.056128f, 0.057805f, 0.059511f, 0.061246f, 0.063010f, 0.064803f, 0.066626f, 0.068478f, 0.070360f, 0.072272f, + 0.074214f, 0.076185f, 0.078187f, 0.080220f, 0.082283f, 0.084376f, 0.086500f, 0.088656f, 0.090842f, 0.093059f, 0.095307f, + 0.097587f, 0.099899f, 0.102242f, 0.104616f, 0.107023f, 0.109462f, 0.111932f, 0.114435f, 0.116971f, 0.119538f, 0.122139f, + 0.124772f, 0.127438f, 0.130136f, 0.132868f, 0.135633f, 0.138432f, 0.141263f, 0.144128f, 0.147027f, 0.149960f, 0.152926f, + 0.155926f, 0.158961f, 0.162029f, 0.165132f, 0.168269f, 0.171441f, 0.174647f, 0.177888f, 0.181164f, 0.184475f, 0.187821f, + 0.191202f, 0.194618f, 0.198069f, 0.201556f, 0.205079f, 0.208637f, 0.212231f, 0.215861f, 0.219526f, 0.223228f, 0.226966f, + 0.230740f, 0.234551f, 0.238398f, 0.242281f, 0.246201f, 0.250158f, 0.254152f, 0.258183f, 0.262251f, 0.266356f, 0.270498f, + 0.274677f, 0.278894f, 0.283149f, 0.287441f, 0.291771f, 0.296138f, 0.300544f, 0.304987f, 0.309469f, 0.313989f, 0.318547f, + 0.323143f, 0.327778f, 0.332452f, 0.337164f, 0.341914f, 0.346704f, 0.351533f, 0.356400f, 0.361307f, 0.366253f, 0.371238f, + 0.376262f, 0.381326f, 0.386430f, 0.391573f, 0.396755f, 0.401978f, 0.407240f, 0.412543f, 0.417885f, 0.423268f, 0.428691f, + 0.434154f, 0.439657f, 0.445201f, 0.450786f, 0.456411f, 0.462077f, 0.467784f, 0.473532f, 0.479320f, 0.485150f, 0.491021f, + 0.496933f, 0.502887f, 0.508881f, 0.514918f, 0.520996f, 0.527115f, 0.533276f, 0.539480f, 0.545725f, 0.552011f, 0.558340f, + 0.564712f, 0.571125f, 0.577581f, 0.584078f, 0.590619f, 0.597202f, 0.603827f, 0.610496f, 0.617207f, 0.623960f, 0.630757f, + 0.637597f, 0.644480f, 0.651406f, 0.658375f, 0.665387f, 0.672443f, 0.679543f, 0.686685f, 0.693872f, 0.701102f, 0.708376f, + 0.715694f, 0.723055f, 0.730461f, 0.737911f, 0.745404f, 0.752942f, 0.760525f, 0.768151f, 0.775822f, 0.783538f, 0.791298f, + 0.799103f, 0.806952f, 0.814847f, 0.822786f, 0.830770f, 0.838799f, 0.846873f, 0.854993f, 0.863157f, 0.871367f, 0.879622f, + 0.887923f, 0.896269f, 0.904661f, 0.913099f, 0.921582f, 0.930111f, 0.938686f, 0.947307f, 0.955974f, 0.964686f, 0.973445f, + 0.982251f, 0.991102f, 1.0f +}; + +static float stbir__srgb_to_linear(float f) +{ + if (f <= 0.04045f) + return f / 12.92f; + else + return (float)pow((f + 0.055f) / 1.055f, 2.4f); +} + +static float stbir__linear_to_srgb(float f) +{ + if (f <= 0.0031308f) + return f * 12.92f; + else + return 1.055f * (float)pow(f, 1 / 2.4f) - 0.055f; +} + +#ifndef STBIR_NON_IEEE_FLOAT +// From https://gist.github.com/rygorous/2203834 + +typedef union +{ + stbir_uint32 u; + float f; +} stbir__FP32; + +static const stbir_uint32 fp32_to_srgb8_tab4[104] = { + 0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 0x00a1000d, + 0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 0x00f4001a, 0x0101001a, + 0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 0x018f0033, 0x01a80033, 0x01c20033, + 0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067, + 0x037800ce, 0x03df00ce, 0x044600ce, 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5, + 0x06970158, 0x07420142, 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2, + 0x0b0f01cb, 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143, + 0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 0x182401af, + 0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 0x21520256, 0x227d0240, + 0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 0x2d1d0341, 0x2ebe031f, 0x304d0300, + 0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401, + 0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559, + 0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723, +}; + +static stbir_uint8 stbir__linear_to_srgb_uchar(float in) +{ + static const stbir__FP32 almostone = { 0x3f7fffff }; // 1-eps + static const stbir__FP32 minval = { (127-13) << 23 }; + stbir_uint32 tab,bias,scale,t; + stbir__FP32 f; + + // Clamp to [2^(-13), 1-eps]; these two values map to 0 and 1, respectively. + // The tests are carefully written so that NaNs map to 0, same as in the reference + // implementation. + if (!(in > minval.f)) // written this way to catch NaNs + in = minval.f; + if (in > almostone.f) + in = almostone.f; + + // Do the table lookup and unpack bias, scale + f.f = in; + tab = fp32_to_srgb8_tab4[(f.u - minval.u) >> 20]; + bias = (tab >> 16) << 9; + scale = tab & 0xffff; + + // Grab next-highest mantissa bits and perform linear interpolation + t = (f.u >> 12) & 0xff; + return (unsigned char) ((bias + scale*t) >> 16); +} + +#else +// sRGB transition values, scaled by 1<<28 +static int stbir__srgb_offset_to_linear_scaled[256] = +{ + 0, 40738, 122216, 203693, 285170, 366648, 448125, 529603, + 611080, 692557, 774035, 855852, 942009, 1033024, 1128971, 1229926, + 1335959, 1447142, 1563542, 1685229, 1812268, 1944725, 2082664, 2226148, + 2375238, 2529996, 2690481, 2856753, 3028870, 3206888, 3390865, 3580856, + 3776916, 3979100, 4187460, 4402049, 4622919, 4850123, 5083710, 5323731, + 5570236, 5823273, 6082892, 6349140, 6622065, 6901714, 7188133, 7481369, + 7781466, 8088471, 8402427, 8723380, 9051372, 9386448, 9728650, 10078021, + 10434603, 10798439, 11169569, 11548036, 11933879, 12327139, 12727857, 13136073, + 13551826, 13975156, 14406100, 14844697, 15290987, 15745007, 16206795, 16676389, + 17153826, 17639142, 18132374, 18633560, 19142734, 19659934, 20185196, 20718552, + 21260042, 21809696, 22367554, 22933648, 23508010, 24090680, 24681686, 25281066, + 25888850, 26505076, 27129772, 27762974, 28404716, 29055026, 29713942, 30381490, + 31057708, 31742624, 32436272, 33138682, 33849884, 34569912, 35298800, 36036568, + 36783260, 37538896, 38303512, 39077136, 39859796, 40651528, 41452360, 42262316, + 43081432, 43909732, 44747252, 45594016, 46450052, 47315392, 48190064, 49074096, + 49967516, 50870356, 51782636, 52704392, 53635648, 54576432, 55526772, 56486700, + 57456236, 58435408, 59424248, 60422780, 61431036, 62449032, 63476804, 64514376, + 65561776, 66619028, 67686160, 68763192, 69850160, 70947088, 72053992, 73170912, + 74297864, 75434880, 76581976, 77739184, 78906536, 80084040, 81271736, 82469648, + 83677792, 84896192, 86124888, 87363888, 88613232, 89872928, 91143016, 92423512, + 93714432, 95015816, 96327688, 97650056, 98982952, 100326408, 101680440, 103045072, + 104420320, 105806224, 107202800, 108610064, 110028048, 111456776, 112896264, 114346544, + 115807632, 117279552, 118762328, 120255976, 121760536, 123276016, 124802440, 126339832, + 127888216, 129447616, 131018048, 132599544, 134192112, 135795792, 137410592, 139036528, + 140673648, 142321952, 143981456, 145652208, 147334208, 149027488, 150732064, 152447968, + 154175200, 155913792, 157663776, 159425168, 161197984, 162982240, 164777968, 166585184, + 168403904, 170234160, 172075968, 173929344, 175794320, 177670896, 179559120, 181458992, + 183370528, 185293776, 187228736, 189175424, 191133888, 193104112, 195086128, 197079968, + 199085648, 201103184, 203132592, 205173888, 207227120, 209292272, 211369392, 213458480, + 215559568, 217672656, 219797792, 221934976, 224084240, 226245600, 228419056, 230604656, + 232802400, 235012320, 237234432, 239468736, 241715280, 243974080, 246245120, 248528464, + 250824112, 253132064, 255452368, 257785040, 260130080, 262487520, 264857376, 267239664, +}; + +static stbir_uint8 stbir__linear_to_srgb_uchar(float f) +{ + int x = (int) (f * (1 << 28)); // has headroom so you don't need to clamp + int v = 0; + int i; + + // Refine the guess with a short binary search. + i = v + 128; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 64; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 32; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 16; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 8; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 4; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 2; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + i = v + 1; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i; + + return (stbir_uint8) v; +} +#endif + +static float stbir__filter_trapezoid(float x, float scale) +{ + float halfscale = scale / 2; + float t = 0.5f + halfscale; + STBIR_ASSERT(scale <= 1); + + x = (float)fabs(x); + + if (x >= t) + return 0; + else + { + float r = 0.5f - halfscale; + if (x <= r) + return 1; + else + return (t - x) / scale; + } +} + +static float stbir__support_trapezoid(float scale) +{ + STBIR_ASSERT(scale <= 1); + return 0.5f + scale / 2; +} + +static float stbir__filter_triangle(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x <= 1.0f) + return 1 - x; + else + return 0; +} + +static float stbir__filter_cubic(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return (4 + x*x*(3*x - 6))/6; + else if (x < 2.0f) + return (8 + x*(-12 + x*(6 - x)))/6; + + return (0.0f); +} + +static float stbir__filter_catmullrom(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return 1 - x*x*(2.5f - 1.5f*x); + else if (x < 2.0f) + return 2 - x*(4 + x*(0.5f*x - 2.5f)); + + return (0.0f); +} + +static float stbir__filter_mitchell(float x, float s) +{ + STBIR__UNUSED_PARAM(s); + + x = (float)fabs(x); + + if (x < 1.0f) + return (16 + x*x*(21 * x - 36))/18; + else if (x < 2.0f) + return (32 + x*(-60 + x*(36 - 7*x)))/18; + + return (0.0f); +} + +static float stbir__support_zero(float s) +{ + STBIR__UNUSED_PARAM(s); + return 0; +} + +static float stbir__support_one(float s) +{ + STBIR__UNUSED_PARAM(s); + return 1; +} + +static float stbir__support_two(float s) +{ + STBIR__UNUSED_PARAM(s); + return 2; +} + +static stbir__filter_info stbir__filter_info_table[] = { + { NULL, stbir__support_zero }, + { stbir__filter_trapezoid, stbir__support_trapezoid }, + { stbir__filter_triangle, stbir__support_one }, + { stbir__filter_cubic, stbir__support_two }, + { stbir__filter_catmullrom, stbir__support_two }, + { stbir__filter_mitchell, stbir__support_two }, +}; + +stbir__inline static int stbir__use_upsampling(float ratio) +{ + return ratio > 1; +} + +stbir__inline static int stbir__use_width_upsampling(stbir__info* stbir_info) +{ + return stbir__use_upsampling(stbir_info->horizontal_scale); +} + +stbir__inline static int stbir__use_height_upsampling(stbir__info* stbir_info) +{ + return stbir__use_upsampling(stbir_info->vertical_scale); +} + +// This is the maximum number of input samples that can affect an output sample +// with the given filter +static int stbir__get_filter_pixel_width(stbir_filter filter, float scale) +{ + STBIR_ASSERT(filter != 0); + STBIR_ASSERT(filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + + if (stbir__use_upsampling(scale)) + return (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2); + else + return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2 / scale); +} + +// This is how much to expand buffers to account for filters seeking outside +// the image boundaries. +static int stbir__get_filter_pixel_margin(stbir_filter filter, float scale) +{ + return stbir__get_filter_pixel_width(filter, scale) / 2; +} + +static int stbir__get_coefficient_width(stbir_filter filter, float scale) +{ + if (stbir__use_upsampling(scale)) + return (int)ceil(stbir__filter_info_table[filter].support(1 / scale) * 2); + else + return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2); +} + +static int stbir__get_contributors(float scale, stbir_filter filter, int input_size, int output_size) +{ + if (stbir__use_upsampling(scale)) + return output_size; + else + return (input_size + stbir__get_filter_pixel_margin(filter, scale) * 2); +} + +static int stbir__get_total_horizontal_coefficients(stbir__info* info) +{ + return info->horizontal_num_contributors + * stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale); +} + +static int stbir__get_total_vertical_coefficients(stbir__info* info) +{ + return info->vertical_num_contributors + * stbir__get_coefficient_width (info->vertical_filter, info->vertical_scale); +} + +static stbir__contributors* stbir__get_contributor(stbir__contributors* contributors, int n) +{ + return &contributors[n]; +} + +// For perf reasons this code is duplicated in stbir__resample_horizontal_upsample/downsample, +// if you change it here change it there too. +static float* stbir__get_coefficient(float* coefficients, stbir_filter filter, float scale, int n, int c) +{ + int width = stbir__get_coefficient_width(filter, scale); + return &coefficients[width*n + c]; +} + +static int stbir__edge_wrap_slow(stbir_edge edge, int n, int max) +{ + switch (edge) + { + case STBIR_EDGE_ZERO: + return 0; // we'll decode the wrong pixel here, and then overwrite with 0s later + + case STBIR_EDGE_CLAMP: + if (n < 0) + return 0; + + if (n >= max) + return max - 1; + + return n; // NOTREACHED + + case STBIR_EDGE_REFLECT: + { + if (n < 0) + { + if (n < max) + return -n; + else + return max - 1; + } + + if (n >= max) + { + int max2 = max * 2; + if (n >= max2) + return 0; + else + return max2 - n - 1; + } + + return n; // NOTREACHED + } + + case STBIR_EDGE_WRAP: + if (n >= 0) + return (n % max); + else + { + int m = (-n) % max; + + if (m != 0) + m = max - m; + + return (m); + } + // NOTREACHED + + default: + STBIR_ASSERT(!"Unimplemented edge type"); + return 0; + } +} + +stbir__inline static int stbir__edge_wrap(stbir_edge edge, int n, int max) +{ + // avoid per-pixel switch + if (n >= 0 && n < max) + return n; + return stbir__edge_wrap_slow(edge, n, max); +} + +// What input pixels contribute to this output pixel? +static void stbir__calculate_sample_range_upsample(int n, float out_filter_radius, float scale_ratio, float out_shift, int* in_first_pixel, int* in_last_pixel, float* in_center_of_out) +{ + float out_pixel_center = (float)n + 0.5f; + float out_pixel_influence_lowerbound = out_pixel_center - out_filter_radius; + float out_pixel_influence_upperbound = out_pixel_center + out_filter_radius; + + float in_pixel_influence_lowerbound = (out_pixel_influence_lowerbound + out_shift) / scale_ratio; + float in_pixel_influence_upperbound = (out_pixel_influence_upperbound + out_shift) / scale_ratio; + + *in_center_of_out = (out_pixel_center + out_shift) / scale_ratio; + *in_first_pixel = (int)(floor(in_pixel_influence_lowerbound + 0.5)); + *in_last_pixel = (int)(floor(in_pixel_influence_upperbound - 0.5)); +} + +// What output pixels does this input pixel contribute to? +static void stbir__calculate_sample_range_downsample(int n, float in_pixels_radius, float scale_ratio, float out_shift, int* out_first_pixel, int* out_last_pixel, float* out_center_of_in) +{ + float in_pixel_center = (float)n + 0.5f; + float in_pixel_influence_lowerbound = in_pixel_center - in_pixels_radius; + float in_pixel_influence_upperbound = in_pixel_center + in_pixels_radius; + + float out_pixel_influence_lowerbound = in_pixel_influence_lowerbound * scale_ratio - out_shift; + float out_pixel_influence_upperbound = in_pixel_influence_upperbound * scale_ratio - out_shift; + + *out_center_of_in = in_pixel_center * scale_ratio - out_shift; + *out_first_pixel = (int)(floor(out_pixel_influence_lowerbound + 0.5)); + *out_last_pixel = (int)(floor(out_pixel_influence_upperbound - 0.5)); +} + +static void stbir__calculate_coefficients_upsample(stbir_filter filter, float scale, int in_first_pixel, int in_last_pixel, float in_center_of_out, stbir__contributors* contributor, float* coefficient_group) +{ + int i; + float total_filter = 0; + float filter_scale; + + STBIR_ASSERT(in_last_pixel - in_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical. + + contributor->n0 = in_first_pixel; + contributor->n1 = in_last_pixel; + + STBIR_ASSERT(contributor->n1 >= contributor->n0); + + for (i = 0; i <= in_last_pixel - in_first_pixel; i++) + { + float in_pixel_center = (float)(i + in_first_pixel) + 0.5f; + coefficient_group[i] = stbir__filter_info_table[filter].kernel(in_center_of_out - in_pixel_center, 1 / scale); + + // If the coefficient is zero, skip it. (Don't do the <0 check here, we want the influence of those outside pixels.) + if (i == 0 && !coefficient_group[i]) + { + contributor->n0 = ++in_first_pixel; + i--; + continue; + } + + total_filter += coefficient_group[i]; + } + + // NOTE(fg): Not actually true in general, nor is there any reason to expect it should be. + // It would be true in exact math but is at best approximately true in floating-point math, + // and it would not make sense to try and put actual bounds on this here because it depends + // on the image aspect ratio which can get pretty extreme. + //STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(in_last_pixel + 1) + 0.5f - in_center_of_out, 1/scale) == 0); + + STBIR_ASSERT(total_filter > 0.9); + STBIR_ASSERT(total_filter < 1.1f); // Make sure it's not way off. + + // Make sure the sum of all coefficients is 1. + filter_scale = 1 / total_filter; + + for (i = 0; i <= in_last_pixel - in_first_pixel; i++) + coefficient_group[i] *= filter_scale; + + for (i = in_last_pixel - in_first_pixel; i >= 0; i--) + { + if (coefficient_group[i]) + break; + + // This line has no weight. We can skip it. + contributor->n1 = contributor->n0 + i - 1; + } +} + +static void stbir__calculate_coefficients_downsample(stbir_filter filter, float scale_ratio, int out_first_pixel, int out_last_pixel, float out_center_of_in, stbir__contributors* contributor, float* coefficient_group) +{ + int i; + + STBIR_ASSERT(out_last_pixel - out_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(scale_ratio) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical. + + contributor->n0 = out_first_pixel; + contributor->n1 = out_last_pixel; + + STBIR_ASSERT(contributor->n1 >= contributor->n0); + + for (i = 0; i <= out_last_pixel - out_first_pixel; i++) + { + float out_pixel_center = (float)(i + out_first_pixel) + 0.5f; + float x = out_pixel_center - out_center_of_in; + coefficient_group[i] = stbir__filter_info_table[filter].kernel(x, scale_ratio) * scale_ratio; + } + + // NOTE(fg): Not actually true in general, nor is there any reason to expect it should be. + // It would be true in exact math but is at best approximately true in floating-point math, + // and it would not make sense to try and put actual bounds on this here because it depends + // on the image aspect ratio which can get pretty extreme. + //STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(out_last_pixel + 1) + 0.5f - out_center_of_in, scale_ratio) == 0); + + for (i = out_last_pixel - out_first_pixel; i >= 0; i--) + { + if (coefficient_group[i]) + break; + + // This line has no weight. We can skip it. + contributor->n1 = contributor->n0 + i - 1; + } +} + +static void stbir__normalize_downsample_coefficients(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, int input_size, int output_size) +{ + int num_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size); + int num_coefficients = stbir__get_coefficient_width(filter, scale_ratio); + int i, j; + int skip; + + for (i = 0; i < output_size; i++) + { + float scale; + float total = 0; + + for (j = 0; j < num_contributors; j++) + { + if (i >= contributors[j].n0 && i <= contributors[j].n1) + { + float coefficient = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0); + total += coefficient; + } + else if (i < contributors[j].n0) + break; + } + + STBIR_ASSERT(total > 0.9f); + STBIR_ASSERT(total < 1.1f); + + scale = 1 / total; + + for (j = 0; j < num_contributors; j++) + { + if (i >= contributors[j].n0 && i <= contributors[j].n1) + *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0) *= scale; + else if (i < contributors[j].n0) + break; + } + } + + // Optimize: Skip zero coefficients and contributions outside of image bounds. + // Do this after normalizing because normalization depends on the n0/n1 values. + for (j = 0; j < num_contributors; j++) + { + int range, max, width; + + skip = 0; + while (*stbir__get_coefficient(coefficients, filter, scale_ratio, j, skip) == 0) + skip++; + + contributors[j].n0 += skip; + + while (contributors[j].n0 < 0) + { + contributors[j].n0++; + skip++; + } + + range = contributors[j].n1 - contributors[j].n0 + 1; + max = stbir__min(num_coefficients, range); + + width = stbir__get_coefficient_width(filter, scale_ratio); + for (i = 0; i < max; i++) + { + if (i + skip >= width) + break; + + *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i) = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i + skip); + } + + continue; + } + + // Using min to avoid writing into invalid pixels. + for (i = 0; i < num_contributors; i++) + contributors[i].n1 = stbir__min(contributors[i].n1, output_size - 1); +} + +// Each scan line uses the same kernel values so we should calculate the kernel +// values once and then we can use them for every scan line. +static void stbir__calculate_filters(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, float shift, int input_size, int output_size) +{ + int n; + int total_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size); + + if (stbir__use_upsampling(scale_ratio)) + { + float out_pixels_radius = stbir__filter_info_table[filter].support(1 / scale_ratio) * scale_ratio; + + // Looping through out pixels + for (n = 0; n < total_contributors; n++) + { + float in_center_of_out; // Center of the current out pixel in the in pixel space + int in_first_pixel, in_last_pixel; + + stbir__calculate_sample_range_upsample(n, out_pixels_radius, scale_ratio, shift, &in_first_pixel, &in_last_pixel, &in_center_of_out); + + stbir__calculate_coefficients_upsample(filter, scale_ratio, in_first_pixel, in_last_pixel, in_center_of_out, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0)); + } + } + else + { + float in_pixels_radius = stbir__filter_info_table[filter].support(scale_ratio) / scale_ratio; + + // Looping through in pixels + for (n = 0; n < total_contributors; n++) + { + float out_center_of_in; // Center of the current out pixel in the in pixel space + int out_first_pixel, out_last_pixel; + int n_adjusted = n - stbir__get_filter_pixel_margin(filter, scale_ratio); + + stbir__calculate_sample_range_downsample(n_adjusted, in_pixels_radius, scale_ratio, shift, &out_first_pixel, &out_last_pixel, &out_center_of_in); + + stbir__calculate_coefficients_downsample(filter, scale_ratio, out_first_pixel, out_last_pixel, out_center_of_in, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0)); + } + + stbir__normalize_downsample_coefficients(contributors, coefficients, filter, scale_ratio, input_size, output_size); + } +} + +static float* stbir__get_decode_buffer(stbir__info* stbir_info) +{ + // The 0 index of the decode buffer starts after the margin. This makes + // it okay to use negative indexes on the decode buffer. + return &stbir_info->decode_buffer[stbir_info->horizontal_filter_pixel_margin * stbir_info->channels]; +} + +#define STBIR__DECODE(type, colorspace) ((int)(type) * (STBIR_MAX_COLORSPACES) + (int)(colorspace)) + +static void stbir__decode_scanline(stbir__info* stbir_info, int n) +{ + int c; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int input_w = stbir_info->input_w; + size_t input_stride_bytes = stbir_info->input_stride_bytes; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir_edge edge_horizontal = stbir_info->edge_horizontal; + stbir_edge edge_vertical = stbir_info->edge_vertical; + size_t in_buffer_row_offset = stbir__edge_wrap(edge_vertical, n, stbir_info->input_h) * input_stride_bytes; + const void* input_data = (char *) stbir_info->input_data + in_buffer_row_offset; + int max_x = input_w + stbir_info->horizontal_filter_pixel_margin; + int decode = STBIR__DECODE(type, colorspace); + + int x = -stbir_info->horizontal_filter_pixel_margin; + + // special handling for STBIR_EDGE_ZERO because it needs to return an item that doesn't appear in the input, + // and we want to avoid paying overhead on every pixel if not STBIR_EDGE_ZERO + if (edge_vertical == STBIR_EDGE_ZERO && (n < 0 || n >= stbir_info->input_h)) + { + for (; x < max_x; x++) + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + return; + } + + switch (decode) + { + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((float)((const unsigned char*)input_data)[input_pixel_index + c]) / stbir__max_uint8_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_uchar_to_linear_float[((const unsigned char*)input_data)[input_pixel_index + c]]; + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned char*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint8_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned short*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint16_as_float; + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear((float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float)); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = ((const float*)input_data)[input_pixel_index + c]; + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB): + for (; x < max_x; x++) + { + int decode_pixel_index = x * channels; + int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels; + for (c = 0; c < channels; c++) + decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((const float*)input_data)[input_pixel_index + c]); + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + decode_buffer[decode_pixel_index + alpha_channel] = ((const float*)input_data)[input_pixel_index + alpha_channel]; + } + + break; + + default: + STBIR_ASSERT(!"Unknown type/colorspace/channels combination."); + break; + } + + if (!(stbir_info->flags & STBIR_FLAG_ALPHA_PREMULTIPLIED)) + { + for (x = -stbir_info->horizontal_filter_pixel_margin; x < max_x; x++) + { + int decode_pixel_index = x * channels; + + // If the alpha value is 0 it will clobber the color values. Make sure it's not. + float alpha = decode_buffer[decode_pixel_index + alpha_channel]; +#ifndef STBIR_NO_ALPHA_EPSILON + if (stbir_info->type != STBIR_TYPE_FLOAT) { + alpha += STBIR_ALPHA_EPSILON; + decode_buffer[decode_pixel_index + alpha_channel] = alpha; + } +#endif + for (c = 0; c < channels; c++) + { + if (c == alpha_channel) + continue; + + decode_buffer[decode_pixel_index + c] *= alpha; + } + } + } + + if (edge_horizontal == STBIR_EDGE_ZERO) + { + for (x = -stbir_info->horizontal_filter_pixel_margin; x < 0; x++) + { + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + } + for (x = input_w; x < max_x; x++) + { + for (c = 0; c < channels; c++) + decode_buffer[x*channels + c] = 0; + } + } +} + +static float* stbir__get_ring_buffer_entry(float* ring_buffer, int index, int ring_buffer_length) +{ + return &ring_buffer[index * ring_buffer_length]; +} + +static float* stbir__add_empty_ring_buffer_entry(stbir__info* stbir_info, int n) +{ + int ring_buffer_index; + float* ring_buffer; + + stbir_info->ring_buffer_last_scanline = n; + + if (stbir_info->ring_buffer_begin_index < 0) + { + ring_buffer_index = stbir_info->ring_buffer_begin_index = 0; + stbir_info->ring_buffer_first_scanline = n; + } + else + { + ring_buffer_index = (stbir_info->ring_buffer_begin_index + (stbir_info->ring_buffer_last_scanline - stbir_info->ring_buffer_first_scanline)) % stbir_info->ring_buffer_num_entries; + STBIR_ASSERT(ring_buffer_index != stbir_info->ring_buffer_begin_index); + } + + ring_buffer = stbir__get_ring_buffer_entry(stbir_info->ring_buffer, ring_buffer_index, stbir_info->ring_buffer_length_bytes / sizeof(float)); + memset(ring_buffer, 0, stbir_info->ring_buffer_length_bytes); + + return ring_buffer; +} + + +static void stbir__resample_horizontal_upsample(stbir__info* stbir_info, float* output_buffer) +{ + int x, k; + int output_w = stbir_info->output_w; + int channels = stbir_info->channels; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors; + float* horizontal_coefficients = stbir_info->horizontal_coefficients; + int coefficient_width = stbir_info->horizontal_coefficient_width; + + for (x = 0; x < output_w; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int out_pixel_index = x * channels; + int coefficient_group = coefficient_width * x; + int coefficient_counter = 0; + + STBIR_ASSERT(n1 >= n0); + STBIR_ASSERT(n0 >= -stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n1 >= -stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n0 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin); + STBIR_ASSERT(n1 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin); + + switch (channels) { + case 1: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 1; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + } + break; + case 2: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 2; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + } + break; + case 3: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 3; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + } + break; + case 4: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * 4; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + STBIR_ASSERT(coefficient != 0); + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient; + } + break; + default: + for (k = n0; k <= n1; k++) + { + int in_pixel_index = k * channels; + float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++]; + int c; + STBIR_ASSERT(coefficient != 0); + for (c = 0; c < channels; c++) + output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient; + } + break; + } + } +} + +static void stbir__resample_horizontal_downsample(stbir__info* stbir_info, float* output_buffer) +{ + int x, k; + int input_w = stbir_info->input_w; + int channels = stbir_info->channels; + float* decode_buffer = stbir__get_decode_buffer(stbir_info); + stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors; + float* horizontal_coefficients = stbir_info->horizontal_coefficients; + int coefficient_width = stbir_info->horizontal_coefficient_width; + int filter_pixel_margin = stbir_info->horizontal_filter_pixel_margin; + int max_x = input_w + filter_pixel_margin * 2; + + STBIR_ASSERT(!stbir__use_width_upsampling(stbir_info)); + + switch (channels) { + case 1: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 1; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 1; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + } + } + break; + + case 2: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 2; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 2; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + } + } + break; + + case 3: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 3; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 3; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + } + } + break; + + case 4: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * 4; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int out_pixel_index = k * 4; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient; + output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient; + output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient; + output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient; + } + } + break; + + default: + for (x = 0; x < max_x; x++) + { + int n0 = horizontal_contributors[x].n0; + int n1 = horizontal_contributors[x].n1; + + int in_x = x - filter_pixel_margin; + int in_pixel_index = in_x * channels; + int max_n = n1; + int coefficient_group = coefficient_width * x; + + for (k = n0; k <= max_n; k++) + { + int c; + int out_pixel_index = k * channels; + float coefficient = horizontal_coefficients[coefficient_group + k - n0]; + for (c = 0; c < channels; c++) + output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient; + } + } + break; + } +} + +static void stbir__decode_and_resample_upsample(stbir__info* stbir_info, int n) +{ + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline(stbir_info, n); + + // Now resample it into the ring buffer. + if (stbir__use_width_upsampling(stbir_info)) + stbir__resample_horizontal_upsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n)); + else + stbir__resample_horizontal_downsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n)); + + // Now it's sitting in the ring buffer ready to be used as source for the vertical sampling. +} + +static void stbir__decode_and_resample_downsample(stbir__info* stbir_info, int n) +{ + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline(stbir_info, n); + + memset(stbir_info->horizontal_buffer, 0, stbir_info->output_w * stbir_info->channels * sizeof(float)); + + // Now resample it into the horizontal buffer. + if (stbir__use_width_upsampling(stbir_info)) + stbir__resample_horizontal_upsample(stbir_info, stbir_info->horizontal_buffer); + else + stbir__resample_horizontal_downsample(stbir_info, stbir_info->horizontal_buffer); + + // Now it's sitting in the horizontal buffer ready to be distributed into the ring buffers. +} + +// Get the specified scan line from the ring buffer. +static float* stbir__get_ring_buffer_scanline(int get_scanline, float* ring_buffer, int begin_index, int first_scanline, int ring_buffer_num_entries, int ring_buffer_length) +{ + int ring_buffer_index = (begin_index + (get_scanline - first_scanline)) % ring_buffer_num_entries; + return stbir__get_ring_buffer_entry(ring_buffer, ring_buffer_index, ring_buffer_length); +} + + +static void stbir__encode_scanline(stbir__info* stbir_info, int num_pixels, void *output_buffer, float *encode_buffer, int channels, int alpha_channel, int decode) +{ + int x; + int n; + int num_nonalpha; + stbir_uint16 nonalpha[STBIR_MAX_CHANNELS]; + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)) + { + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + float alpha = encode_buffer[pixel_index + alpha_channel]; + float reciprocal_alpha = alpha ? 1.0f / alpha : 0; + + // unrolling this produced a 1% slowdown upscaling a large RGBA linear-space image on my machine - stb + for (n = 0; n < channels; n++) + if (n != alpha_channel) + encode_buffer[pixel_index + n] *= reciprocal_alpha; + + // We added in a small epsilon to prevent the color channel from being deleted with zero alpha. + // Because we only add it for integer types, it will automatically be discarded on integer + // conversion, so we don't need to subtract it back out (which would be problematic for + // numeric precision reasons). + } + } + + // build a table of all channels that need colorspace correction, so + // we don't perform colorspace correction on channels that don't need it. + for (x = 0, num_nonalpha = 0; x < channels; ++x) + { + if (x != alpha_channel || (stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE)) + { + nonalpha[num_nonalpha++] = (stbir_uint16)x; + } + } + + #define STBIR__ROUND_INT(f) ((int) ((f)+0.5)) + #define STBIR__ROUND_UINT(f) ((stbir_uint32) ((f)+0.5)) + + #ifdef STBIR__SATURATE_INT + #define STBIR__ENCODE_LINEAR8(f) stbir__saturate8 (STBIR__ROUND_INT((f) * stbir__max_uint8_as_float )) + #define STBIR__ENCODE_LINEAR16(f) stbir__saturate16(STBIR__ROUND_INT((f) * stbir__max_uint16_as_float)) + #else + #define STBIR__ENCODE_LINEAR8(f) (unsigned char ) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint8_as_float ) + #define STBIR__ENCODE_LINEAR16(f) (unsigned short) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint16_as_float) + #endif + + switch (decode) + { + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned char*)output_buffer)[index] = STBIR__ENCODE_LINEAR8(encode_buffer[index]); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned char*)output_buffer)[index] = stbir__linear_to_srgb_uchar(encode_buffer[index]); + } + + if (!(stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned char *)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR8(encode_buffer[pixel_index+alpha_channel]); + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned short*)output_buffer)[index] = STBIR__ENCODE_LINEAR16(encode_buffer[index]); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned short*)output_buffer)[index] = (unsigned short)STBIR__ROUND_INT(stbir__linear_to_srgb(stbir__saturate(encode_buffer[index])) * stbir__max_uint16_as_float); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned short*)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR16(encode_buffer[pixel_index + alpha_channel]); + } + + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__saturate(encode_buffer[index])) * stbir__max_uint32_as_float); + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__linear_to_srgb(stbir__saturate(encode_buffer[index]))) * stbir__max_uint32_as_float); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((unsigned int*)output_buffer)[pixel_index + alpha_channel] = (unsigned int)STBIR__ROUND_INT(((double)stbir__saturate(encode_buffer[pixel_index + alpha_channel])) * stbir__max_uint32_as_float); + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < channels; n++) + { + int index = pixel_index + n; + ((float*)output_buffer)[index] = encode_buffer[index]; + } + } + break; + + case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB): + for (x=0; x < num_pixels; ++x) + { + int pixel_index = x*channels; + + for (n = 0; n < num_nonalpha; n++) + { + int index = pixel_index + nonalpha[n]; + ((float*)output_buffer)[index] = stbir__linear_to_srgb(encode_buffer[index]); + } + + if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE)) + ((float*)output_buffer)[pixel_index + alpha_channel] = encode_buffer[pixel_index + alpha_channel]; + } + break; + + default: + STBIR_ASSERT(!"Unknown type/colorspace/channels combination."); + break; + } +} + +static void stbir__resample_vertical_upsample(stbir__info* stbir_info, int n) +{ + int x, k; + int output_w = stbir_info->output_w; + stbir__contributors* vertical_contributors = stbir_info->vertical_contributors; + float* vertical_coefficients = stbir_info->vertical_coefficients; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int ring_buffer_entries = stbir_info->ring_buffer_num_entries; + void* output_data = stbir_info->output_data; + float* encode_buffer = stbir_info->encode_buffer; + int decode = STBIR__DECODE(type, colorspace); + int coefficient_width = stbir_info->vertical_coefficient_width; + int coefficient_counter; + int contributor = n; + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index; + int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + + int n0,n1, output_row_start; + int coefficient_group = coefficient_width * contributor; + + n0 = vertical_contributors[contributor].n0; + n1 = vertical_contributors[contributor].n1; + + output_row_start = n * stbir_info->output_stride_bytes; + + STBIR_ASSERT(stbir__use_height_upsampling(stbir_info)); + + memset(encode_buffer, 0, output_w * sizeof(float) * channels); + + // I tried reblocking this for better cache usage of encode_buffer + // (using x_outer, k, x_inner), but it lost speed. -- stb + + coefficient_counter = 0; + switch (channels) { + case 1: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 1; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + } + } + break; + case 2: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 2; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + } + } + break; + case 3: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 3; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient; + } + } + break; + case 4: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * 4; + encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient; + encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient; + encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient; + encode_buffer[in_pixel_index + 3] += ring_buffer_entry[in_pixel_index + 3] * coefficient; + } + } + break; + default: + for (k = n0; k <= n1; k++) + { + int coefficient_index = coefficient_counter++; + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + for (x = 0; x < output_w; ++x) + { + int in_pixel_index = x * channels; + int c; + for (c = 0; c < channels; c++) + encode_buffer[in_pixel_index + c] += ring_buffer_entry[in_pixel_index + c] * coefficient; + } + } + break; + } + stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, encode_buffer, channels, alpha_channel, decode); +} + +static void stbir__resample_vertical_downsample(stbir__info* stbir_info, int n) +{ + int x, k; + int output_w = stbir_info->output_w; + stbir__contributors* vertical_contributors = stbir_info->vertical_contributors; + float* vertical_coefficients = stbir_info->vertical_coefficients; + int channels = stbir_info->channels; + int ring_buffer_entries = stbir_info->ring_buffer_num_entries; + float* horizontal_buffer = stbir_info->horizontal_buffer; + int coefficient_width = stbir_info->vertical_coefficient_width; + int contributor = n + stbir_info->vertical_filter_pixel_margin; + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index; + int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + int n0,n1; + + n0 = vertical_contributors[contributor].n0; + n1 = vertical_contributors[contributor].n1; + + STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info)); + + for (k = n0; k <= n1; k++) + { + int coefficient_index = k - n0; + int coefficient_group = coefficient_width * contributor; + float coefficient = vertical_coefficients[coefficient_group + coefficient_index]; + + float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length); + + switch (channels) { + case 1: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 1; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + } + break; + case 2: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 2; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + } + break; + case 3: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 3; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient; + } + break; + case 4: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * 4; + ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient; + ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient; + ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient; + ring_buffer_entry[in_pixel_index + 3] += horizontal_buffer[in_pixel_index + 3] * coefficient; + } + break; + default: + for (x = 0; x < output_w; x++) + { + int in_pixel_index = x * channels; + + int c; + for (c = 0; c < channels; c++) + ring_buffer_entry[in_pixel_index + c] += horizontal_buffer[in_pixel_index + c] * coefficient; + } + break; + } + } +} + +static void stbir__buffer_loop_upsample(stbir__info* stbir_info) +{ + int y; + float scale_ratio = stbir_info->vertical_scale; + float out_scanlines_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(1/scale_ratio) * scale_ratio; + + STBIR_ASSERT(stbir__use_height_upsampling(stbir_info)); + + for (y = 0; y < stbir_info->output_h; y++) + { + float in_center_of_out = 0; // Center of the current out scanline in the in scanline space + int in_first_scanline = 0, in_last_scanline = 0; + + stbir__calculate_sample_range_upsample(y, out_scanlines_radius, scale_ratio, stbir_info->vertical_shift, &in_first_scanline, &in_last_scanline, &in_center_of_out); + + STBIR_ASSERT(in_last_scanline - in_first_scanline + 1 <= stbir_info->ring_buffer_num_entries); + + if (stbir_info->ring_buffer_begin_index >= 0) + { + // Get rid of whatever we don't need anymore. + while (in_first_scanline > stbir_info->ring_buffer_first_scanline) + { + if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline) + { + // We just popped the last scanline off the ring buffer. + // Reset it to the empty state. + stbir_info->ring_buffer_begin_index = -1; + stbir_info->ring_buffer_first_scanline = 0; + stbir_info->ring_buffer_last_scanline = 0; + break; + } + else + { + stbir_info->ring_buffer_first_scanline++; + stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries; + } + } + } + + // Load in new ones. + if (stbir_info->ring_buffer_begin_index < 0) + stbir__decode_and_resample_upsample(stbir_info, in_first_scanline); + + while (in_last_scanline > stbir_info->ring_buffer_last_scanline) + stbir__decode_and_resample_upsample(stbir_info, stbir_info->ring_buffer_last_scanline + 1); + + // Now all buffers should be ready to write a row of vertical sampling. + stbir__resample_vertical_upsample(stbir_info, y); + + STBIR_PROGRESS_REPORT((float)y / stbir_info->output_h); + } +} + +static void stbir__empty_ring_buffer(stbir__info* stbir_info, int first_necessary_scanline) +{ + int output_stride_bytes = stbir_info->output_stride_bytes; + int channels = stbir_info->channels; + int alpha_channel = stbir_info->alpha_channel; + int type = stbir_info->type; + int colorspace = stbir_info->colorspace; + int output_w = stbir_info->output_w; + void* output_data = stbir_info->output_data; + int decode = STBIR__DECODE(type, colorspace); + + float* ring_buffer = stbir_info->ring_buffer; + int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float); + + if (stbir_info->ring_buffer_begin_index >= 0) + { + // Get rid of whatever we don't need anymore. + while (first_necessary_scanline > stbir_info->ring_buffer_first_scanline) + { + if (stbir_info->ring_buffer_first_scanline >= 0 && stbir_info->ring_buffer_first_scanline < stbir_info->output_h) + { + int output_row_start = stbir_info->ring_buffer_first_scanline * output_stride_bytes; + float* ring_buffer_entry = stbir__get_ring_buffer_entry(ring_buffer, stbir_info->ring_buffer_begin_index, ring_buffer_length); + stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, ring_buffer_entry, channels, alpha_channel, decode); + STBIR_PROGRESS_REPORT((float)stbir_info->ring_buffer_first_scanline / stbir_info->output_h); + } + + if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline) + { + // We just popped the last scanline off the ring buffer. + // Reset it to the empty state. + stbir_info->ring_buffer_begin_index = -1; + stbir_info->ring_buffer_first_scanline = 0; + stbir_info->ring_buffer_last_scanline = 0; + break; + } + else + { + stbir_info->ring_buffer_first_scanline++; + stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries; + } + } + } +} + +static void stbir__buffer_loop_downsample(stbir__info* stbir_info) +{ + int y; + float scale_ratio = stbir_info->vertical_scale; + int output_h = stbir_info->output_h; + float in_pixels_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(scale_ratio) / scale_ratio; + int pixel_margin = stbir_info->vertical_filter_pixel_margin; + int max_y = stbir_info->input_h + pixel_margin; + + STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info)); + + for (y = -pixel_margin; y < max_y; y++) + { + float out_center_of_in; // Center of the current out scanline in the in scanline space + int out_first_scanline, out_last_scanline; + + stbir__calculate_sample_range_downsample(y, in_pixels_radius, scale_ratio, stbir_info->vertical_shift, &out_first_scanline, &out_last_scanline, &out_center_of_in); + + STBIR_ASSERT(out_last_scanline - out_first_scanline + 1 <= stbir_info->ring_buffer_num_entries); + + if (out_last_scanline < 0 || out_first_scanline >= output_h) + continue; + + stbir__empty_ring_buffer(stbir_info, out_first_scanline); + + stbir__decode_and_resample_downsample(stbir_info, y); + + // Load in new ones. + if (stbir_info->ring_buffer_begin_index < 0) + stbir__add_empty_ring_buffer_entry(stbir_info, out_first_scanline); + + while (out_last_scanline > stbir_info->ring_buffer_last_scanline) + stbir__add_empty_ring_buffer_entry(stbir_info, stbir_info->ring_buffer_last_scanline + 1); + + // Now the horizontal buffer is ready to write to all ring buffer rows. + stbir__resample_vertical_downsample(stbir_info, y); + } + + stbir__empty_ring_buffer(stbir_info, stbir_info->output_h); +} + +static void stbir__setup(stbir__info *info, int input_w, int input_h, int output_w, int output_h, int channels) +{ + info->input_w = input_w; + info->input_h = input_h; + info->output_w = output_w; + info->output_h = output_h; + info->channels = channels; +} + +static void stbir__calculate_transform(stbir__info *info, float s0, float t0, float s1, float t1, float *transform) +{ + info->s0 = s0; + info->t0 = t0; + info->s1 = s1; + info->t1 = t1; + + if (transform) + { + info->horizontal_scale = transform[0]; + info->vertical_scale = transform[1]; + info->horizontal_shift = transform[2]; + info->vertical_shift = transform[3]; + } + else + { + info->horizontal_scale = ((float)info->output_w / info->input_w) / (s1 - s0); + info->vertical_scale = ((float)info->output_h / info->input_h) / (t1 - t0); + + info->horizontal_shift = s0 * info->output_w / (s1 - s0); + info->vertical_shift = t0 * info->output_h / (t1 - t0); + } +} + +static void stbir__choose_filter(stbir__info *info, stbir_filter h_filter, stbir_filter v_filter) +{ + if (h_filter == 0) + h_filter = stbir__use_upsampling(info->horizontal_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE; + if (v_filter == 0) + v_filter = stbir__use_upsampling(info->vertical_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE; + info->horizontal_filter = h_filter; + info->vertical_filter = v_filter; +} + +static stbir_uint32 stbir__calculate_memory(stbir__info *info) +{ + int pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale); + int filter_height = stbir__get_filter_pixel_width(info->vertical_filter, info->vertical_scale); + + info->horizontal_num_contributors = stbir__get_contributors(info->horizontal_scale, info->horizontal_filter, info->input_w, info->output_w); + info->vertical_num_contributors = stbir__get_contributors(info->vertical_scale , info->vertical_filter , info->input_h, info->output_h); + + // One extra entry because floating point precision problems sometimes cause an extra to be necessary. + info->ring_buffer_num_entries = filter_height + 1; + + info->horizontal_contributors_size = info->horizontal_num_contributors * sizeof(stbir__contributors); + info->horizontal_coefficients_size = stbir__get_total_horizontal_coefficients(info) * sizeof(float); + info->vertical_contributors_size = info->vertical_num_contributors * sizeof(stbir__contributors); + info->vertical_coefficients_size = stbir__get_total_vertical_coefficients(info) * sizeof(float); + info->decode_buffer_size = (info->input_w + pixel_margin * 2) * info->channels * sizeof(float); + info->horizontal_buffer_size = info->output_w * info->channels * sizeof(float); + info->ring_buffer_size = info->output_w * info->channels * info->ring_buffer_num_entries * sizeof(float); + info->encode_buffer_size = info->output_w * info->channels * sizeof(float); + + STBIR_ASSERT(info->horizontal_filter != 0); + STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late + STBIR_ASSERT(info->vertical_filter != 0); + STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late + + if (stbir__use_height_upsampling(info)) + // The horizontal buffer is for when we're downsampling the height and we + // can't output the result of sampling the decode buffer directly into the + // ring buffers. + info->horizontal_buffer_size = 0; + else + // The encode buffer is to retain precision in the height upsampling method + // and isn't used when height downsampling. + info->encode_buffer_size = 0; + + return info->horizontal_contributors_size + info->horizontal_coefficients_size + + info->vertical_contributors_size + info->vertical_coefficients_size + + info->decode_buffer_size + info->horizontal_buffer_size + + info->ring_buffer_size + info->encode_buffer_size; +} + +static int stbir__resize_allocated(stbir__info *info, + const void* input_data, int input_stride_in_bytes, + void* output_data, int output_stride_in_bytes, + int alpha_channel, stbir_uint32 flags, stbir_datatype type, + stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace, + void* tempmem, size_t tempmem_size_in_bytes) +{ + size_t memory_required = stbir__calculate_memory(info); + + int width_stride_input = input_stride_in_bytes ? input_stride_in_bytes : info->channels * info->input_w * stbir__type_size[type]; + int width_stride_output = output_stride_in_bytes ? output_stride_in_bytes : info->channels * info->output_w * stbir__type_size[type]; + +#ifdef STBIR_DEBUG_OVERWRITE_TEST +#define OVERWRITE_ARRAY_SIZE 8 + unsigned char overwrite_output_before_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_tempmem_before_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_output_after_pre[OVERWRITE_ARRAY_SIZE]; + unsigned char overwrite_tempmem_after_pre[OVERWRITE_ARRAY_SIZE]; + + size_t begin_forbidden = width_stride_output * (info->output_h - 1) + info->output_w * info->channels * stbir__type_size[type]; + memcpy(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE); + memcpy(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE); +#endif + + STBIR_ASSERT(info->channels >= 0); + STBIR_ASSERT(info->channels <= STBIR_MAX_CHANNELS); + + if (info->channels < 0 || info->channels > STBIR_MAX_CHANNELS) + return 0; + + STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); + + if (info->horizontal_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table)) + return 0; + if (info->vertical_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table)) + return 0; + + if (alpha_channel < 0) + flags |= STBIR_FLAG_ALPHA_USES_COLORSPACE | STBIR_FLAG_ALPHA_PREMULTIPLIED; + + if (!(flags&STBIR_FLAG_ALPHA_USES_COLORSPACE) || !(flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)) { + STBIR_ASSERT(alpha_channel >= 0 && alpha_channel < info->channels); + } + + if (alpha_channel >= info->channels) + return 0; + + STBIR_ASSERT(tempmem); + + if (!tempmem) + return 0; + + STBIR_ASSERT(tempmem_size_in_bytes >= memory_required); + + if (tempmem_size_in_bytes < memory_required) + return 0; + + memset(tempmem, 0, tempmem_size_in_bytes); + + info->input_data = input_data; + info->input_stride_bytes = width_stride_input; + + info->output_data = output_data; + info->output_stride_bytes = width_stride_output; + + info->alpha_channel = alpha_channel; + info->flags = flags; + info->type = type; + info->edge_horizontal = edge_horizontal; + info->edge_vertical = edge_vertical; + info->colorspace = colorspace; + + info->horizontal_coefficient_width = stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale); + info->vertical_coefficient_width = stbir__get_coefficient_width (info->vertical_filter , info->vertical_scale ); + info->horizontal_filter_pixel_width = stbir__get_filter_pixel_width (info->horizontal_filter, info->horizontal_scale); + info->vertical_filter_pixel_width = stbir__get_filter_pixel_width (info->vertical_filter , info->vertical_scale ); + info->horizontal_filter_pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale); + info->vertical_filter_pixel_margin = stbir__get_filter_pixel_margin(info->vertical_filter , info->vertical_scale ); + + info->ring_buffer_length_bytes = info->output_w * info->channels * sizeof(float); + info->decode_buffer_pixels = info->input_w + info->horizontal_filter_pixel_margin * 2; + +#define STBIR__NEXT_MEMPTR(current, newtype) (newtype*)(((unsigned char*)current) + current##_size) + + info->horizontal_contributors = (stbir__contributors *) tempmem; + info->horizontal_coefficients = STBIR__NEXT_MEMPTR(info->horizontal_contributors, float); + info->vertical_contributors = STBIR__NEXT_MEMPTR(info->horizontal_coefficients, stbir__contributors); + info->vertical_coefficients = STBIR__NEXT_MEMPTR(info->vertical_contributors, float); + info->decode_buffer = STBIR__NEXT_MEMPTR(info->vertical_coefficients, float); + + if (stbir__use_height_upsampling(info)) + { + info->horizontal_buffer = NULL; + info->ring_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float); + info->encode_buffer = STBIR__NEXT_MEMPTR(info->ring_buffer, float); + + STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->encode_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes); + } + else + { + info->horizontal_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float); + info->ring_buffer = STBIR__NEXT_MEMPTR(info->horizontal_buffer, float); + info->encode_buffer = NULL; + + STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->ring_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes); + } + +#undef STBIR__NEXT_MEMPTR + + // This signals that the ring buffer is empty + info->ring_buffer_begin_index = -1; + + stbir__calculate_filters(info->horizontal_contributors, info->horizontal_coefficients, info->horizontal_filter, info->horizontal_scale, info->horizontal_shift, info->input_w, info->output_w); + stbir__calculate_filters(info->vertical_contributors, info->vertical_coefficients, info->vertical_filter, info->vertical_scale, info->vertical_shift, info->input_h, info->output_h); + + STBIR_PROGRESS_REPORT(0); + + if (stbir__use_height_upsampling(info)) + stbir__buffer_loop_upsample(info); + else + stbir__buffer_loop_downsample(info); + + STBIR_PROGRESS_REPORT(1); + +#ifdef STBIR_DEBUG_OVERWRITE_TEST + STBIR_ASSERT(memcmp(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0); + STBIR_ASSERT(memcmp(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE) == 0); +#endif + + return 1; +} + + +static int stbir__resize_arbitrary( + void *alloc_context, + const void* input_data, int input_w, int input_h, int input_stride_in_bytes, + void* output_data, int output_w, int output_h, int output_stride_in_bytes, + float s0, float t0, float s1, float t1, float *transform, + int channels, int alpha_channel, stbir_uint32 flags, stbir_datatype type, + stbir_filter h_filter, stbir_filter v_filter, + stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace) +{ + stbir__info info; + int result; + size_t memory_required; + void* extra_memory; + + stbir__setup(&info, input_w, input_h, output_w, output_h, channels); + stbir__calculate_transform(&info, s0,t0,s1,t1,transform); + stbir__choose_filter(&info, h_filter, v_filter); + memory_required = stbir__calculate_memory(&info); + extra_memory = STBIR_MALLOC(memory_required, alloc_context); + + if (!extra_memory) + return 0; + + result = stbir__resize_allocated(&info, input_data, input_stride_in_bytes, + output_data, output_stride_in_bytes, + alpha_channel, flags, type, + edge_horizontal, edge_vertical, + colorspace, extra_memory, memory_required); + + STBIR_FREE(extra_memory, alloc_context); + + return result; +} + +STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR); +} + +STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_FLOAT, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR); +} + +STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB); +} + +STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode) +{ + return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT, + edge_wrap_mode, edge_wrap_mode, STBIR_COLORSPACE_SRGB); +} + +STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + +STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT16, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + + +STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + float *output_pixels , int output_w, int output_h, int output_stride_in_bytes, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space, + void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_FLOAT, filter, filter, + edge_wrap_mode, edge_wrap_mode, space); +} + + +STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + + +STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float x_scale, float y_scale, + float x_offset, float y_offset) +{ + float transform[4]; + transform[0] = x_scale; + transform[1] = y_scale; + transform[2] = x_offset; + transform[3] = y_offset; + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + 0,0,1,1,transform,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + +STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_datatype datatype, + int num_channels, int alpha_channel, int flags, + stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical, + stbir_filter filter_horizontal, stbir_filter filter_vertical, + stbir_colorspace space, void *alloc_context, + float s0, float t0, float s1, float t1) +{ + return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + s0,t0,s1,t1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical, + edge_mode_horizontal, edge_mode_vertical, space); +} + +#endif // STB_IMAGE_RESIZE_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/stb_image_resize2.h b/contrib/tinyusdz/tinyusdz_repo/src/external/stb_image_resize2.h new file mode 100644 index 000000000..1cd379a72 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/stb_image_resize2.h @@ -0,0 +1,10365 @@ +/* stb_image_resize2 - v2.06 - public domain image resizing + + by Jeff Roberts (v2) and Jorge L Rodriguez + http://github.com/nothings/stb + + Can be threaded with the extended API. SSE2, AVX, Neon and WASM SIMD support. Only + scaling and translation is supported, no rotations or shears. + + COMPILING & LINKING + In one C/C++ file that #includes this file, do this: + #define STB_IMAGE_RESIZE_IMPLEMENTATION + before the #include. That will create the implementation in that file. + + PORTING FROM VERSION 1 + + The API has changed. You can continue to use the old version of stb_image_resize.h, + which is available in the "deprecated/" directory. + + If you're using the old simple-to-use API, porting is straightforward. + (For more advanced APIs, read the documentation.) + + stbir_resize_uint8(): + - call `stbir_resize_uint8_linear`, cast channel count to `stbir_pixel_layout` + + stbir_resize_float(): + - call `stbir_resize_float_linear`, cast channel count to `stbir_pixel_layout` + + stbir_resize_uint8_srgb(): + - function name is unchanged + - cast channel count to `stbir_pixel_layout` + - above is sufficient unless your image has alpha and it's not RGBA/BGRA + - in that case, follow the below instructions for stbir_resize_uint8_srgb_edgemode + + stbir_resize_uint8_srgb_edgemode() + - switch to the "medium complexity" API + - stbir_resize(), very similar API but a few more parameters: + - pixel_layout: cast channel count to `stbir_pixel_layout` + - data_type: STBIR_TYPE_UINT8_SRGB + - edge: unchanged (STBIR_EDGE_WRAP, etc.) + - filter: STBIR_FILTER_DEFAULT + - which channel is alpha is specified in stbir_pixel_layout, see enum for details + + EASY API CALLS: + Easy API downsamples w/Mitchell filter, upsamples w/cubic interpolation, clamps to edge. + + stbir_resize_uint8_srgb( input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout_enum ) + + stbir_resize_uint8_linear( input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout_enum ) + + stbir_resize_float_linear( input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout_enum ) + + If you pass NULL or zero for the output_pixels, we will allocate the output buffer + for you and return it from the function (free with free() or STBIR_FREE). + As a special case, XX_stride_in_bytes of 0 means packed continuously in memory. + + API LEVELS + There are three levels of API - easy-to-use, medium-complexity and extended-complexity. + + See the "header file" section of the source for API documentation. + + ADDITIONAL DOCUMENTATION + + MEMORY ALLOCATION + By default, we use malloc and free for memory allocation. To override the + memory allocation, before the implementation #include, add a: + + #define STBIR_MALLOC(size,user_data) ... + #define STBIR_FREE(ptr,user_data) ... + + Each resize makes exactly one call to malloc/free (unless you use the + extended API where you can do one allocation for many resizes). Under + address sanitizer, we do separate allocations to find overread/writes. + + PERFORMANCE + This library was written with an emphasis on performance. When testing + stb_image_resize with RGBA, the fastest mode is STBIR_4CHANNEL with + STBIR_TYPE_UINT8 pixels and CLAMPed edges (which is what many other resize + libs do by default). Also, make sure SIMD is turned on of course (default + for 64-bit targets). Avoid WRAP edge mode if you want the fastest speed. + + This library also comes with profiling built-in. If you define STBIR_PROFILE, + you can use the advanced API and get low-level profiling information by + calling stbir_resize_extended_profile_info() or stbir_resize_split_profile_info() + after a resize. + + SIMD + Most of the routines have optimized SSE2, AVX, NEON and WASM versions. + + On Microsoft compilers, we automatically turn on SIMD for 64-bit x64 and + ARM; for 32-bit x86 and ARM, you select SIMD mode by defining STBIR_SSE2 or + STBIR_NEON. For AVX and AVX2, we auto-select it by detecting the /arch:AVX + or /arch:AVX2 switches. You can also always manually turn SSE2, AVX or AVX2 + support on by defining STBIR_SSE2, STBIR_AVX or STBIR_AVX2. + + On Linux, SSE2 and Neon is on by default for 64-bit x64 or ARM64. For 32-bit, + we select x86 SIMD mode by whether you have -msse2, -mavx or -mavx2 enabled + on the command line. For 32-bit ARM, you must pass -mfpu=neon-vfpv4 for both + clang and GCC, but GCC also requires an additional -mfp16-format=ieee to + automatically enable NEON. + + On x86 platforms, you can also define STBIR_FP16C to turn on FP16C instructions + for converting back and forth to half-floats. This is autoselected when we + are using AVX2. Clang and GCC also require the -mf16c switch. ARM always uses + the built-in half float hardware NEON instructions. + + You can also tell us to use multiply-add instructions with STBIR_USE_FMA. + Because x86 doesn't always have fma, we turn it off by default to maintain + determinism across all platforms. If you don't care about non-FMA determinism + and are willing to restrict yourself to more recent x86 CPUs (around the AVX + timeframe), then fma will give you around a 15% speedup. + + You can force off SIMD in all cases by defining STBIR_NO_SIMD. You can turn + off AVX or AVX2 specifically with STBIR_NO_AVX or STBIR_NO_AVX2. AVX is 10% + to 40% faster, and AVX2 is generally another 12%. + + ALPHA CHANNEL + Most of the resizing functions provide the ability to control how the alpha + channel of an image is processed. + + When alpha represents transparency, it is important that when combining + colors with filtering, the pixels should not be treated equally; they + should use a weighted average based on their alpha values. For example, + if a pixel is 1% opaque bright green and another pixel is 99% opaque + black and you average them, the average will be 50% opaque, but the + unweighted average and will be a middling green color, while the weighted + average will be nearly black. This means the unweighted version introduced + green energy that didn't exist in the source image. + + (If you want to know why this makes sense, you can work out the math for + the following: consider what happens if you alpha composite a source image + over a fixed color and then average the output, vs. if you average the + source image pixels and then composite that over the same fixed color. + Only the weighted average produces the same result as the ground truth + composite-then-average result.) + + Therefore, it is in general best to "alpha weight" the pixels when applying + filters to them. This essentially means multiplying the colors by the alpha + values before combining them, and then dividing by the alpha value at the + end. + + The computer graphics industry introduced a technique called "premultiplied + alpha" or "associated alpha" in which image colors are stored in image files + already multiplied by their alpha. This saves some math when compositing, + and also avoids the need to divide by the alpha at the end (which is quite + inefficient). However, while premultiplied alpha is common in the movie CGI + industry, it is not commonplace in other industries like videogames, and most + consumer file formats are generally expected to contain not-premultiplied + colors. For example, Photoshop saves PNG files "unpremultiplied", and web + browsers like Chrome and Firefox expect PNG images to be unpremultiplied. + + Note that there are three possibilities that might describe your image + and resize expectation: + + 1. images are not premultiplied, alpha weighting is desired + 2. images are not premultiplied, alpha weighting is not desired + 3. images are premultiplied + + Both case #2 and case #3 require the exact same math: no alpha weighting + should be applied or removed. Only case 1 requires extra math operations; + the other two cases can be handled identically. + + stb_image_resize expects case #1 by default, applying alpha weighting to + images, expecting the input images to be unpremultiplied. This is what the + COLOR+ALPHA buffer types tell the resizer to do. + + When you use the pixel layouts STBIR_RGBA, STBIR_BGRA, STBIR_ARGB, + STBIR_ABGR, STBIR_RX, or STBIR_XR you are telling us that the pixels are + non-premultiplied. In these cases, the resizer will alpha weight the colors + (effectively creating the premultiplied image), do the filtering, and then + convert back to non-premult on exit. + + When you use the pixel layouts STBIR_RGBA_PM, STBIR_RGBA_PM, STBIR_RGBA_PM, + STBIR_RGBA_PM, STBIR_RX_PM or STBIR_XR_PM, you are telling that the pixels + ARE premultiplied. In this case, the resizer doesn't have to do the + premultipling - it can filter directly on the input. This about twice as + fast as the non-premultiplied case, so it's the right option if your data is + already setup correctly. + + When you use the pixel layout STBIR_4CHANNEL or STBIR_2CHANNEL, you are + telling us that there is no channel that represents transparency; it may be + RGB and some unrelated fourth channel that has been stored in the alpha + channel, but it is actually not alpha. No special processing will be + performed. + + The difference between the generic 4 or 2 channel layouts, and the + specialized _PM versions is with the _PM versions you are telling us that + the data *is* alpha, just don't premultiply it. That's important when + using SRGB pixel formats, we need to know where the alpha is, because + it is converted linearly (rather than with the SRGB converters). + + Because alpha weighting produces the same effect as premultiplying, you + even have the option with non-premultiplied inputs to let the resizer + produce a premultiplied output. Because the intially computed alpha-weighted + output image is effectively premultiplied, this is actually more performant + than the normal path which un-premultiplies the output image as a final step. + + Finally, when converting both in and out of non-premulitplied space (for + example, when using STBIR_RGBA), we go to somewhat heroic measures to + ensure that areas with zero alpha value pixels get something reasonable + in the RGB values. If you don't care about the RGB values of zero alpha + pixels, you can call the stbir_set_non_pm_alpha_speed_over_quality() + function - this runs a premultiplied resize about 25% faster. That said, + when you really care about speed, using premultiplied pixels for both in + and out (STBIR_RGBA_PM, etc) much faster than both of these premultiplied + options. + + PIXEL LAYOUT CONVERSION + The resizer can convert from some pixel layouts to others. When using the + stbir_set_pixel_layouts(), you can, for example, specify STBIR_RGBA + on input, and STBIR_ARGB on output, and it will re-organize the channels + during the resize. Currently, you can only convert between two pixel + layouts with the same number of channels. + + DETERMINISM + We commit to being deterministic (from x64 to ARM to scalar to SIMD, etc). + This requires compiling with fast-math off (using at least /fp:precise). + Also, you must turn off fp-contracting (which turns mult+adds into fmas)! + We attempt to do this with pragmas, but with Clang, you usually want to add + -ffp-contract=off to the command line as well. + + For 32-bit x86, you must use SSE and SSE2 codegen for determinism. That is, + if the scalar x87 unit gets used at all, we immediately lose determinism. + On Microsoft Visual Studio 2008 and earlier, from what we can tell there is + no way to be deterministic in 32-bit x86 (some x87 always leaks in, even + with fp:strict). On 32-bit x86 GCC, determinism requires both -msse2 and + -fpmath=sse. + + Note that we will not be deterministic with float data containing NaNs - + the NaNs will propagate differently on different SIMD and platforms. + + If you turn on STBIR_USE_FMA, then we will be deterministic with other + fma targets, but we will differ from non-fma targets (this is unavoidable, + because a fma isn't simply an add with a mult - it also introduces a + rounding difference compared to non-fma instruction sequences. + + FLOAT PIXEL FORMAT RANGE + Any range of values can be used for the non-alpha float data that you pass + in (0 to 1, -1 to 1, whatever). However, if you are inputting float values + but *outputting* bytes or shorts, you must use a range of 0 to 1 so that we + scale back properly. The alpha channel must also be 0 to 1 for any format + that does premultiplication prior to resizing. + + Note also that with float output, using filters with negative lobes, the + output filtered values might go slightly out of range. You can define + STBIR_FLOAT_LOW_CLAMP and/or STBIR_FLOAT_HIGH_CLAMP to specify the range + to clamp to on output, if that's important. + + MAX/MIN SCALE FACTORS + The input pixel resolutions are in integers, and we do the internal pointer + resolution in size_t sized integers. However, the scale ratio from input + resolution to output resolution is calculated in float form. This means + the effective possible scale ratio is limited to 24 bits (or 16 million + to 1). As you get close to the size of the float resolution (again, 16 + million pixels wide or high), you might start seeing float inaccuracy + issues in general in the pipeline. If you have to do extreme resizes, + you can usually do this is multiple stages (using float intermediate + buffers). + + FLIPPED IMAGES + Stride is just the delta from one scanline to the next. This means you can + use a negative stride to handle inverted images (point to the final + scanline and use a negative stride). You can invert the input or output, + using negative strides. + + DEFAULT FILTERS + For functions which don't provide explicit control over what filters to + use, you can change the compile-time defaults with: + + #define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_something + #define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_something + + See stbir_filter in the header-file section for the list of filters. + + NEW FILTERS + A number of 1D filter kernels are supplied. For a list of supported + filters, see the stbir_filter enum. You can install your own filters by + using the stbir_set_filter_callbacks function. + + PROGRESS + For interactive use with slow resize operations, you can use the the + scanline callbacks in the extended API. It would have to be a *very* large + image resample to need progress though - we're very fast. + + CEIL and FLOOR + In scalar mode, the only functions we use from math.h are ceilf and floorf, + but if you have your own versions, you can define the STBIR_CEILF(v) and + STBIR_FLOORF(v) macros and we'll use them instead. In SIMD, we just use + our own versions. + + ASSERT + Define STBIR_ASSERT(boolval) to override assert() and not use assert.h + + FUTURE TODOS + * For polyphase integral filters, we just memcpy the coeffs to dupe + them, but we should indirect and use the same coeff memory. + * Add pixel layout conversions for sensible different channel counts + (maybe, 1->3/4, 3->4, 4->1, 3->1). + * For SIMD encode and decode scanline routines, do any pre-aligning + for bad input/output buffer alignments and pitch? + * For very wide scanlines, we should we do vertical strips to stay within + L2 cache. Maybe do chunks of 1K pixels at a time. There would be + some pixel reconversion, but probably dwarfed by things falling out + of cache. Probably also something possible with alternating between + scattering and gathering at high resize scales? + * Rewrite the coefficient generator to do many at once. + * AVX-512 vertical kernels - worried about downclocking here. + * Convert the reincludes to macros when we know they aren't changing. + * Experiment with pivoting the horizontal and always using the + vertical filters (which are faster, but perhaps not enough to overcome + the pivot cost and the extra memory touches). Need to buffer the whole + image so have to balance memory use. + * Most of our code is internally function pointers, should we compile + all the SIMD stuff always and dynamically dispatch? + + CONTRIBUTORS + Jeff Roberts: 2.0 implementation, optimizations, SIMD + Martins Mozeiko: NEON simd, WASM simd, clang and GCC whisperer. + Fabian Giesen: half float and srgb converters + Sean Barrett: API design, optimizations + Jorge L Rodriguez: Original 1.0 implementation + Aras Pranckevicius: bugfixes + Nathan Reed: warning fixes for 1.0 + + REVISIONS + 2.06 (2024-02-10) fix for indentical width/height 3x or more down-scaling + undersampling a single row on rare resize ratios (about 1%) + 2.05 (2024-02-07) fix for 2 pixel to 1 pixel resizes with wrap (thanks Aras) + fix for output callback (thanks Julien Koenen) + 2.04 (2023-11-17) fix for rare AVX bug, shadowed symbol (thanks Nikola Smiljanic). + 2.03 (2023-11-01) ASAN and TSAN warnings fixed, minor tweaks. + 2.00 (2023-10-10) mostly new source: new api, optimizations, simd, vertical-first, etc + (2x-5x faster without simd, 4x-12x faster with simd) + (in some cases, 20x to 40x faster - resizing to very small for example) + 0.96 (2019-03-04) fixed warnings + 0.95 (2017-07-23) fixed warnings + 0.94 (2017-03-18) fixed warnings + 0.93 (2017-03-03) fixed bug with certain combinations of heights + 0.92 (2017-01-02) fix integer overflow on large (>2GB) images + 0.91 (2016-04-02) fix warnings; fix handling of subpixel regions + 0.90 (2014-09-17) first released version + + LICENSE + See end of file for license information. +*/ + +#if !defined(STB_IMAGE_RESIZE_DO_HORIZONTALS) && !defined(STB_IMAGE_RESIZE_DO_VERTICALS) && !defined(STB_IMAGE_RESIZE_DO_CODERS) // for internal re-includes + +#ifndef STBIR_INCLUDE_STB_IMAGE_RESIZE2_H +#define STBIR_INCLUDE_STB_IMAGE_RESIZE2_H + +#include +#ifdef _MSC_VER +typedef unsigned char stbir_uint8; +typedef unsigned short stbir_uint16; +typedef unsigned int stbir_uint32; +typedef unsigned __int64 stbir_uint64; +#else +#include +typedef uint8_t stbir_uint8; +typedef uint16_t stbir_uint16; +typedef uint32_t stbir_uint32; +typedef uint64_t stbir_uint64; +#endif + +#ifdef _M_IX86_FP +#if ( _M_IX86_FP >= 1 ) +#ifndef STBIR_SSE +#define STBIR_SSE +#endif +#endif +#endif + +#if defined(_x86_64) || defined( __x86_64__ ) || defined( _M_X64 ) || defined(__x86_64) || defined(_M_AMD64) || defined(__SSE2__) || defined(STBIR_SSE) || defined(STBIR_SSE2) + #ifndef STBIR_SSE2 + #define STBIR_SSE2 + #endif + #if defined(__AVX__) || defined(STBIR_AVX2) + #ifndef STBIR_AVX + #ifndef STBIR_NO_AVX + #define STBIR_AVX + #endif + #endif + #endif + #if defined(__AVX2__) || defined(STBIR_AVX2) + #ifndef STBIR_NO_AVX2 + #ifndef STBIR_AVX2 + #define STBIR_AVX2 + #endif + #if defined( _MSC_VER ) && !defined(__clang__) + #ifndef STBIR_FP16C // FP16C instructions are on all AVX2 cpus, so we can autoselect it here on microsoft - clang needs -m16c + #define STBIR_FP16C + #endif + #endif + #endif + #endif + #ifdef __F16C__ + #ifndef STBIR_FP16C // turn on FP16C instructions if the define is set (for clang and gcc) + #define STBIR_FP16C + #endif + #endif +#endif + +#if defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) || defined(_M_ARM) || (__ARM_NEON_FP & 4) != 0 && __ARM_FP16_FORMAT_IEEE != 0 +#ifndef STBIR_NEON +#define STBIR_NEON +#endif +#endif + +#if defined(_M_ARM) +#ifdef STBIR_USE_FMA +#undef STBIR_USE_FMA // no FMA for 32-bit arm on MSVC +#endif +#endif + +#if defined(__wasm__) && defined(__wasm_simd128__) +#ifndef STBIR_WASM +#define STBIR_WASM +#endif +#endif + +#ifndef STBIRDEF +#ifdef STB_IMAGE_RESIZE_STATIC +#define STBIRDEF static +#else +#ifdef __cplusplus +#define STBIRDEF extern "C" +#else +#define STBIRDEF extern +#endif +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +//// start "header file" /////////////////////////////////////////////////// +// +// Easy-to-use API: +// +// * stride is the offset between successive rows of image data +// in memory, in bytes. specify 0 for packed continuously in memory +// * colorspace is linear or sRGB as specified by function name +// * Uses the default filters +// * Uses edge mode clamped +// * returned result is 1 for success or 0 in case of an error. + + +// stbir_pixel_layout specifies: +// number of channels +// order of channels +// whether color is premultiplied by alpha +// for back compatibility, you can cast the old channel count to an stbir_pixel_layout +typedef enum +{ + STBIR_1CHANNEL = 1, + STBIR_2CHANNEL = 2, + STBIR_RGB = 3, // 3-chan, with order specified (for channel flipping) + STBIR_BGR = 0, // 3-chan, with order specified (for channel flipping) + STBIR_4CHANNEL = 5, + + STBIR_RGBA = 4, // alpha formats, where alpha is NOT premultiplied into color channels + STBIR_BGRA = 6, + STBIR_ARGB = 7, + STBIR_ABGR = 8, + STBIR_RA = 9, + STBIR_AR = 10, + + STBIR_RGBA_PM = 11, // alpha formats, where alpha is premultiplied into color channels + STBIR_BGRA_PM = 12, + STBIR_ARGB_PM = 13, + STBIR_ABGR_PM = 14, + STBIR_RA_PM = 15, + STBIR_AR_PM = 16, + + STBIR_RGBA_NO_AW = 11, // alpha formats, where NO alpha weighting is applied at all! + STBIR_BGRA_NO_AW = 12, // these are just synonyms for the _PM flags (which also do + STBIR_ARGB_NO_AW = 13, // no alpha weighting). These names just make it more clear + STBIR_ABGR_NO_AW = 14, // for some folks). + STBIR_RA_NO_AW = 15, + STBIR_AR_NO_AW = 16, + +} stbir_pixel_layout; + +//=============================================================== +// Simple-complexity API +// +// If output_pixels is NULL (0), then we will allocate the buffer and return it to you. +//-------------------------------- + +STBIRDEF unsigned char * stbir_resize_uint8_srgb( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_type ); + +STBIRDEF unsigned char * stbir_resize_uint8_linear( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_type ); + +STBIRDEF float * stbir_resize_float_linear( const float *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_type ); +//=============================================================== + +//=============================================================== +// Medium-complexity API +// +// This extends the easy-to-use API as follows: +// +// * Can specify the datatype - U8, U8_SRGB, U16, FLOAT, HALF_FLOAT +// * Edge wrap can selected explicitly +// * Filter can be selected explicitly +//-------------------------------- + +typedef enum +{ + STBIR_EDGE_CLAMP = 0, + STBIR_EDGE_REFLECT = 1, + STBIR_EDGE_WRAP = 2, // this edge mode is slower and uses more memory + STBIR_EDGE_ZERO = 3, +} stbir_edge; + +typedef enum +{ + STBIR_FILTER_DEFAULT = 0, // use same filter type that easy-to-use API chooses + STBIR_FILTER_BOX = 1, // A trapezoid w/1-pixel wide ramps, same result as box for integer scale ratios + STBIR_FILTER_TRIANGLE = 2, // On upsampling, produces same results as bilinear texture filtering + STBIR_FILTER_CUBICBSPLINE = 3, // The cubic b-spline (aka Mitchell-Netrevalli with B=1,C=0), gaussian-esque + STBIR_FILTER_CATMULLROM = 4, // An interpolating cubic spline + STBIR_FILTER_MITCHELL = 5, // Mitchell-Netrevalli filter with B=1/3, C=1/3 + STBIR_FILTER_POINT_SAMPLE = 6, // Simple point sampling + STBIR_FILTER_OTHER = 7, // User callback specified +} stbir_filter; + +typedef enum +{ + STBIR_TYPE_UINT8 = 0, + STBIR_TYPE_UINT8_SRGB = 1, + STBIR_TYPE_UINT8_SRGB_ALPHA = 2, // alpha channel, when present, should also be SRGB (this is very unusual) + STBIR_TYPE_UINT16 = 3, + STBIR_TYPE_FLOAT = 4, + STBIR_TYPE_HALF_FLOAT = 5 +} stbir_datatype; + +// medium api +STBIRDEF void * stbir_resize( const void *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout, stbir_datatype data_type, + stbir_edge edge, stbir_filter filter ); +//=============================================================== + + + +//=============================================================== +// Extended-complexity API +// +// This API exposes all resize functionality. +// +// * Separate filter types for each axis +// * Separate edge modes for each axis +// * Separate input and output data types +// * Can specify regions with subpixel correctness +// * Can specify alpha flags +// * Can specify a memory callback +// * Can specify a callback data type for pixel input and output +// * Can be threaded for a single resize +// * Can be used to resize many frames without recalculating the sampler info +// +// Use this API as follows: +// 1) Call the stbir_resize_init function on a local STBIR_RESIZE structure +// 2) Call any of the stbir_set functions +// 3) Optionally call stbir_build_samplers() if you are going to resample multiple times +// with the same input and output dimensions (like resizing video frames) +// 4) Resample by calling stbir_resize_extended(). +// 5) Call stbir_free_samplers() if you called stbir_build_samplers() +//-------------------------------- + + +// Types: + +// INPUT CALLBACK: this callback is used for input scanlines +typedef void const * stbir_input_callback( void * optional_output, void const * input_ptr, int num_pixels, int x, int y, void * context ); + +// OUTPUT CALLBACK: this callback is used for output scanlines +typedef void stbir_output_callback( void const * output_ptr, int num_pixels, int y, void * context ); + +// callbacks for user installed filters +typedef float stbir__kernel_callback( float x, float scale, void * user_data ); // centered at zero +typedef float stbir__support_callback( float scale, void * user_data ); + +// internal structure with precomputed scaling +typedef struct stbir__info stbir__info; + +typedef struct STBIR_RESIZE // use the stbir_resize_init and stbir_override functions to set these values for future compatibility +{ + void * user_data; + void const * input_pixels; + int input_w, input_h; + double input_s0, input_t0, input_s1, input_t1; + stbir_input_callback * input_cb; + void * output_pixels; + int output_w, output_h; + int output_subx, output_suby, output_subw, output_subh; + stbir_output_callback * output_cb; + int input_stride_in_bytes; + int output_stride_in_bytes; + int splits; + int fast_alpha; + int needs_rebuild; + int called_alloc; + stbir_pixel_layout input_pixel_layout_public; + stbir_pixel_layout output_pixel_layout_public; + stbir_datatype input_data_type; + stbir_datatype output_data_type; + stbir_filter horizontal_filter, vertical_filter; + stbir_edge horizontal_edge, vertical_edge; + stbir__kernel_callback * horizontal_filter_kernel; stbir__support_callback * horizontal_filter_support; + stbir__kernel_callback * vertical_filter_kernel; stbir__support_callback * vertical_filter_support; + stbir__info * samplers; +} STBIR_RESIZE; + +// extended complexity api + + +// First off, you must ALWAYS call stbir_resize_init on your resize structure before any of the other calls! +STBIRDEF void stbir_resize_init( STBIR_RESIZE * resize, + const void *input_pixels, int input_w, int input_h, int input_stride_in_bytes, // stride can be zero + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, // stride can be zero + stbir_pixel_layout pixel_layout, stbir_datatype data_type ); + +//=============================================================== +// You can update these parameters any time after resize_init and there is no cost +//-------------------------------- + +STBIRDEF void stbir_set_datatypes( STBIR_RESIZE * resize, stbir_datatype input_type, stbir_datatype output_type ); +STBIRDEF void stbir_set_pixel_callbacks( STBIR_RESIZE * resize, stbir_input_callback * input_cb, stbir_output_callback * output_cb ); // no callbacks by default +STBIRDEF void stbir_set_user_data( STBIR_RESIZE * resize, void * user_data ); // pass back STBIR_RESIZE* by default +STBIRDEF void stbir_set_buffer_ptrs( STBIR_RESIZE * resize, const void * input_pixels, int input_stride_in_bytes, void * output_pixels, int output_stride_in_bytes ); + +//=============================================================== + + +//=============================================================== +// If you call any of these functions, you will trigger a sampler rebuild! +//-------------------------------- + +STBIRDEF int stbir_set_pixel_layouts( STBIR_RESIZE * resize, stbir_pixel_layout input_pixel_layout, stbir_pixel_layout output_pixel_layout ); // sets new buffer layouts +STBIRDEF int stbir_set_edgemodes( STBIR_RESIZE * resize, stbir_edge horizontal_edge, stbir_edge vertical_edge ); // CLAMP by default + +STBIRDEF int stbir_set_filters( STBIR_RESIZE * resize, stbir_filter horizontal_filter, stbir_filter vertical_filter ); // STBIR_DEFAULT_FILTER_UPSAMPLE/DOWNSAMPLE by default +STBIRDEF int stbir_set_filter_callbacks( STBIR_RESIZE * resize, stbir__kernel_callback * horizontal_filter, stbir__support_callback * horizontal_support, stbir__kernel_callback * vertical_filter, stbir__support_callback * vertical_support ); + +STBIRDEF int stbir_set_pixel_subrect( STBIR_RESIZE * resize, int subx, int suby, int subw, int subh ); // sets both sub-regions (full regions by default) +STBIRDEF int stbir_set_input_subrect( STBIR_RESIZE * resize, double s0, double t0, double s1, double t1 ); // sets input sub-region (full region by default) +STBIRDEF int stbir_set_output_pixel_subrect( STBIR_RESIZE * resize, int subx, int suby, int subw, int subh ); // sets output sub-region (full region by default) + +// when inputting AND outputting non-premultiplied alpha pixels, we use a slower but higher quality technique +// that fills the zero alpha pixel's RGB values with something plausible. If you don't care about areas of +// zero alpha, you can call this function to get about a 25% speed improvement for STBIR_RGBA to STBIR_RGBA +// types of resizes. +STBIRDEF int stbir_set_non_pm_alpha_speed_over_quality( STBIR_RESIZE * resize, int non_pma_alpha_speed_over_quality ); +//=============================================================== + + +//=============================================================== +// You can call build_samplers to prebuild all the internal data we need to resample. +// Then, if you call resize_extended many times with the same resize, you only pay the +// cost once. +// If you do call build_samplers, you MUST call free_samplers eventually. +//-------------------------------- + +// This builds the samplers and does one allocation +STBIRDEF int stbir_build_samplers( STBIR_RESIZE * resize ); + +// You MUST call this, if you call stbir_build_samplers or stbir_build_samplers_with_splits +STBIRDEF void stbir_free_samplers( STBIR_RESIZE * resize ); +//=============================================================== + + +// And this is the main function to perform the resize synchronously on one thread. +STBIRDEF int stbir_resize_extended( STBIR_RESIZE * resize ); + + +//=============================================================== +// Use these functions for multithreading. +// 1) You call stbir_build_samplers_with_splits first on the main thread +// 2) Then stbir_resize_with_split on each thread +// 3) stbir_free_samplers when done on the main thread +//-------------------------------- + +// This will build samplers for threading. +// You can pass in the number of threads you'd like to use (try_splits). +// It returns the number of splits (threads) that you can call it with. +/// It might be less if the image resize can't be split up that many ways. + +STBIRDEF int stbir_build_samplers_with_splits( STBIR_RESIZE * resize, int try_splits ); + +// This function does a split of the resizing (you call this fuction for each +// split, on multiple threads). A split is a piece of the output resize pixel space. + +// Note that you MUST call stbir_build_samplers_with_splits before stbir_resize_extended_split! + +// Usually, you will always call stbir_resize_split with split_start as the thread_index +// and "1" for the split_count. +// But, if you have a weird situation where you MIGHT want 8 threads, but sometimes +// only 4 threads, you can use 0,2,4,6 for the split_start's and use "2" for the +// split_count each time to turn in into a 4 thread resize. (This is unusual). + +STBIRDEF int stbir_resize_extended_split( STBIR_RESIZE * resize, int split_start, int split_count ); +//=============================================================== + + +//=============================================================== +// Pixel Callbacks info: +//-------------------------------- + +// The input callback is super flexible - it calls you with the input address +// (based on the stride and base pointer), it gives you an optional_output +// pointer that you can fill, or you can just return your own pointer into +// your own data. +// +// You can also do conversion from non-supported data types if necessary - in +// this case, you ignore the input_ptr and just use the x and y parameters to +// calculate your own input_ptr based on the size of each non-supported pixel. +// (Something like the third example below.) +// +// You can also install just an input or just an output callback by setting the +// callback that you don't want to zero. +// +// First example, progress: (getting a callback that you can monitor the progress): +// void const * my_callback( void * optional_output, void const * input_ptr, int num_pixels, int x, int y, void * context ) +// { +// percentage_done = y / input_height; +// return input_ptr; // use buffer from call +// } +// +// Next example, copying: (copy from some other buffer or stream): +// void const * my_callback( void * optional_output, void const * input_ptr, int num_pixels, int x, int y, void * context ) +// { +// CopyOrStreamData( optional_output, other_data_src, num_pixels * pixel_width_in_bytes ); +// return optional_output; // return the optional buffer that we filled +// } +// +// Third example, input another buffer without copying: (zero-copy from other buffer): +// void const * my_callback( void * optional_output, void const * input_ptr, int num_pixels, int x, int y, void * context ) +// { +// void * pixels = ( (char*) other_image_base ) + ( y * other_image_stride ) + ( x * other_pixel_width_in_bytes ); +// return pixels; // return pointer to your data without copying +// } +// +// +// The output callback is considerably simpler - it just calls you so that you can dump +// out each scanline. You could even directly copy out to disk if you have a simple format +// like TGA or BMP. You can also convert to other output types here if you want. +// +// Simple example: +// void const * my_output( void * output_ptr, int num_pixels, int y, void * context ) +// { +// percentage_done = y / output_height; +// fwrite( output_ptr, pixel_width_in_bytes, num_pixels, output_file ); +// } +//=============================================================== + + + + +//=============================================================== +// optional built-in profiling API +//-------------------------------- + +#ifdef STBIR_PROFILE + +typedef struct STBIR_PROFILE_INFO +{ + stbir_uint64 total_clocks; + + // how many clocks spent (of total_clocks) in the various resize routines, along with a string description + // there are "resize_count" number of zones + stbir_uint64 clocks[ 8 ]; + char const ** descriptions; + + // count of clocks and descriptions + stbir_uint32 count; +} STBIR_PROFILE_INFO; + +// use after calling stbir_resize_extended (or stbir_build_samplers or stbir_build_samplers_with_splits) +STBIRDEF void stbir_resize_build_profile_info( STBIR_PROFILE_INFO * out_info, STBIR_RESIZE const * resize ); + +// use after calling stbir_resize_extended +STBIRDEF void stbir_resize_extended_profile_info( STBIR_PROFILE_INFO * out_info, STBIR_RESIZE const * resize ); + +// use after calling stbir_resize_extended_split +STBIRDEF void stbir_resize_split_profile_info( STBIR_PROFILE_INFO * out_info, STBIR_RESIZE const * resize, int split_start, int split_num ); + +//=============================================================== + +#endif + + +//// end header file ///////////////////////////////////////////////////// +#endif // STBIR_INCLUDE_STB_IMAGE_RESIZE2_H + +#if defined(STB_IMAGE_RESIZE_IMPLEMENTATION) || defined(STB_IMAGE_RESIZE2_IMPLEMENTATION) + +#ifndef STBIR_ASSERT +#include +#define STBIR_ASSERT(x) assert(x) +#endif + +#ifndef STBIR_MALLOC +#include +#define STBIR_MALLOC(size,user_data) ((void)(user_data), malloc(size)) +#define STBIR_FREE(ptr,user_data) ((void)(user_data), free(ptr)) +// (we used the comma operator to evaluate user_data, to avoid "unused parameter" warnings) +#endif + +#ifdef _MSC_VER + +#define stbir__inline __forceinline + +#else + +#define stbir__inline __inline__ + +// Clang address sanitizer +#if defined(__has_feature) + #if __has_feature(address_sanitizer) || __has_feature(memory_sanitizer) + #ifndef STBIR__SEPARATE_ALLOCATIONS + #define STBIR__SEPARATE_ALLOCATIONS + #endif + #endif +#endif + +#endif + +// GCC and MSVC +#if defined(__SANITIZE_ADDRESS__) + #ifndef STBIR__SEPARATE_ALLOCATIONS + #define STBIR__SEPARATE_ALLOCATIONS + #endif +#endif + +// Always turn off automatic FMA use - use STBIR_USE_FMA if you want. +// Otherwise, this is a determinism disaster. +#ifndef STBIR_DONT_CHANGE_FP_CONTRACT // override in case you don't want this behavior +#if defined(_MSC_VER) && !defined(__clang__) +#if _MSC_VER > 1200 +#pragma fp_contract(off) +#endif +#elif defined(__GNUC__) && !defined(__clang__) +#pragma GCC optimize("fp-contract=off") +#else +#pragma STDC FP_CONTRACT OFF +#endif +#endif + +#ifdef _MSC_VER +#define STBIR__UNUSED(v) (void)(v) +#else +#define STBIR__UNUSED(v) (void)sizeof(v) +#endif + +#define STBIR__ARRAY_SIZE(a) (sizeof((a))/sizeof((a)[0])) + + +#ifndef STBIR_DEFAULT_FILTER_UPSAMPLE +#define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM +#endif + +#ifndef STBIR_DEFAULT_FILTER_DOWNSAMPLE +#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL +#endif + + +#ifndef STBIR__HEADER_FILENAME +#define STBIR__HEADER_FILENAME "stb_image_resize2.h" +#endif + +// the internal pixel layout enums are in a different order, so we can easily do range comparisons of types +// the public pixel layout is ordered in a way that if you cast num_channels (1-4) to the enum, you get something sensible +typedef enum +{ + STBIRI_1CHANNEL = 0, + STBIRI_2CHANNEL = 1, + STBIRI_RGB = 2, + STBIRI_BGR = 3, + STBIRI_4CHANNEL = 4, + + STBIRI_RGBA = 5, + STBIRI_BGRA = 6, + STBIRI_ARGB = 7, + STBIRI_ABGR = 8, + STBIRI_RA = 9, + STBIRI_AR = 10, + + STBIRI_RGBA_PM = 11, + STBIRI_BGRA_PM = 12, + STBIRI_ARGB_PM = 13, + STBIRI_ABGR_PM = 14, + STBIRI_RA_PM = 15, + STBIRI_AR_PM = 16, +} stbir_internal_pixel_layout; + +// define the public pixel layouts to not compile inside the implementation (to avoid accidental use) +#define STBIR_BGR bad_dont_use_in_implementation +#define STBIR_1CHANNEL STBIR_BGR +#define STBIR_2CHANNEL STBIR_BGR +#define STBIR_RGB STBIR_BGR +#define STBIR_RGBA STBIR_BGR +#define STBIR_4CHANNEL STBIR_BGR +#define STBIR_BGRA STBIR_BGR +#define STBIR_ARGB STBIR_BGR +#define STBIR_ABGR STBIR_BGR +#define STBIR_RA STBIR_BGR +#define STBIR_AR STBIR_BGR +#define STBIR_RGBA_PM STBIR_BGR +#define STBIR_BGRA_PM STBIR_BGR +#define STBIR_ARGB_PM STBIR_BGR +#define STBIR_ABGR_PM STBIR_BGR +#define STBIR_RA_PM STBIR_BGR +#define STBIR_AR_PM STBIR_BGR + +// must match stbir_datatype +static unsigned char stbir__type_size[] = { + 1,1,1,2,4,2 // STBIR_TYPE_UINT8,STBIR_TYPE_UINT8_SRGB,STBIR_TYPE_UINT8_SRGB_ALPHA,STBIR_TYPE_UINT16,STBIR_TYPE_FLOAT,STBIR_TYPE_HALF_FLOAT +}; + +// When gathering, the contributors are which source pixels contribute. +// When scattering, the contributors are which destination pixels are contributed to. +typedef struct +{ + int n0; // First contributing pixel + int n1; // Last contributing pixel +} stbir__contributors; + +typedef struct +{ + int lowest; // First sample index for whole filter + int highest; // Last sample index for whole filter + int widest; // widest single set of samples for an output +} stbir__filter_extent_info; + +typedef struct +{ + int n0; // First pixel of decode buffer to write to + int n1; // Last pixel of decode that will be written to + int pixel_offset_for_input; // Pixel offset into input_scanline +} stbir__span; + +typedef struct stbir__scale_info +{ + int input_full_size; + int output_sub_size; + float scale; + float inv_scale; + float pixel_shift; // starting shift in output pixel space (in pixels) + int scale_is_rational; + stbir_uint32 scale_numerator, scale_denominator; +} stbir__scale_info; + +typedef struct +{ + stbir__contributors * contributors; + float* coefficients; + stbir__contributors * gather_prescatter_contributors; + float * gather_prescatter_coefficients; + stbir__scale_info scale_info; + float support; + stbir_filter filter_enum; + stbir__kernel_callback * filter_kernel; + stbir__support_callback * filter_support; + stbir_edge edge; + int coefficient_width; + int filter_pixel_width; + int filter_pixel_margin; + int num_contributors; + int contributors_size; + int coefficients_size; + stbir__filter_extent_info extent_info; + int is_gather; // 0 = scatter, 1 = gather with scale >= 1, 2 = gather with scale < 1 + int gather_prescatter_num_contributors; + int gather_prescatter_coefficient_width; + int gather_prescatter_contributors_size; + int gather_prescatter_coefficients_size; +} stbir__sampler; + +typedef struct +{ + stbir__contributors conservative; + int edge_sizes[2]; // this can be less than filter_pixel_margin, if the filter and scaling falls off + stbir__span spans[2]; // can be two spans, if doing input subrect with clamp mode WRAP +} stbir__extents; + +typedef struct +{ +#ifdef STBIR_PROFILE + union + { + struct { stbir_uint64 total, looping, vertical, horizontal, decode, encode, alpha, unalpha; } named; + stbir_uint64 array[8]; + } profile; + stbir_uint64 * current_zone_excluded_ptr; +#endif + float* decode_buffer; + + int ring_buffer_first_scanline; + int ring_buffer_last_scanline; + int ring_buffer_begin_index; // first_scanline is at this index in the ring buffer + int start_output_y, end_output_y; + int start_input_y, end_input_y; // used in scatter only + + #ifdef STBIR__SEPARATE_ALLOCATIONS + float** ring_buffers; // one pointer for each ring buffer + #else + float* ring_buffer; // one big buffer that we index into + #endif + + float* vertical_buffer; + + char no_cache_straddle[64]; +} stbir__per_split_info; + +typedef void stbir__decode_pixels_func( float * decode, int width_times_channels, void const * input ); +typedef void stbir__alpha_weight_func( float * decode_buffer, int width_times_channels ); +typedef void stbir__horizontal_gather_channels_func( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, + stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ); +typedef void stbir__alpha_unweight_func(float * encode_buffer, int width_times_channels ); +typedef void stbir__encode_pixels_func( void * output, int width_times_channels, float const * encode ); + +struct stbir__info +{ +#ifdef STBIR_PROFILE + union + { + struct { stbir_uint64 total, build, alloc, horizontal, vertical, cleanup, pivot; } named; + stbir_uint64 array[7]; + } profile; + stbir_uint64 * current_zone_excluded_ptr; +#endif + stbir__sampler horizontal; + stbir__sampler vertical; + + void const * input_data; + void * output_data; + + int input_stride_bytes; + int output_stride_bytes; + int ring_buffer_length_bytes; // The length of an individual entry in the ring buffer. The total number of ring buffers is stbir__get_filter_pixel_width(filter) + int ring_buffer_num_entries; // Total number of entries in the ring buffer. + + stbir_datatype input_type; + stbir_datatype output_type; + + stbir_input_callback * in_pixels_cb; + void * user_data; + stbir_output_callback * out_pixels_cb; + + stbir__extents scanline_extents; + + void * alloced_mem; + stbir__per_split_info * split_info; // by default 1, but there will be N of these allocated based on the thread init you did + + stbir__decode_pixels_func * decode_pixels; + stbir__alpha_weight_func * alpha_weight; + stbir__horizontal_gather_channels_func * horizontal_gather_channels; + stbir__alpha_unweight_func * alpha_unweight; + stbir__encode_pixels_func * encode_pixels; + + int alloced_total; + int splits; // count of splits + + stbir_internal_pixel_layout input_pixel_layout_internal; + stbir_internal_pixel_layout output_pixel_layout_internal; + + int input_color_and_type; + int offset_x, offset_y; // offset within output_data + int vertical_first; + int channels; + int effective_channels; // same as channels, except on RGBA/ARGB (7), or XA/AX (3) + int alloc_ring_buffer_num_entries; // Number of entries in the ring buffer that will be allocated +}; + + +#define stbir__max_uint8_as_float 255.0f +#define stbir__max_uint16_as_float 65535.0f +#define stbir__max_uint8_as_float_inverted (1.0f/255.0f) +#define stbir__max_uint16_as_float_inverted (1.0f/65535.0f) +#define stbir__small_float ((float)1 / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20)) + +// min/max friendly +#define STBIR_CLAMP(x, xmin, xmax) do { \ + if ( (x) < (xmin) ) (x) = (xmin); \ + if ( (x) > (xmax) ) (x) = (xmax); \ +} while (0) + +static stbir__inline int stbir__min(int a, int b) +{ + return a < b ? a : b; +} + +static stbir__inline int stbir__max(int a, int b) +{ + return a > b ? a : b; +} + +static float stbir__srgb_uchar_to_linear_float[256] = { + 0.000000f, 0.000304f, 0.000607f, 0.000911f, 0.001214f, 0.001518f, 0.001821f, 0.002125f, 0.002428f, 0.002732f, 0.003035f, + 0.003347f, 0.003677f, 0.004025f, 0.004391f, 0.004777f, 0.005182f, 0.005605f, 0.006049f, 0.006512f, 0.006995f, 0.007499f, + 0.008023f, 0.008568f, 0.009134f, 0.009721f, 0.010330f, 0.010960f, 0.011612f, 0.012286f, 0.012983f, 0.013702f, 0.014444f, + 0.015209f, 0.015996f, 0.016807f, 0.017642f, 0.018500f, 0.019382f, 0.020289f, 0.021219f, 0.022174f, 0.023153f, 0.024158f, + 0.025187f, 0.026241f, 0.027321f, 0.028426f, 0.029557f, 0.030713f, 0.031896f, 0.033105f, 0.034340f, 0.035601f, 0.036889f, + 0.038204f, 0.039546f, 0.040915f, 0.042311f, 0.043735f, 0.045186f, 0.046665f, 0.048172f, 0.049707f, 0.051269f, 0.052861f, + 0.054480f, 0.056128f, 0.057805f, 0.059511f, 0.061246f, 0.063010f, 0.064803f, 0.066626f, 0.068478f, 0.070360f, 0.072272f, + 0.074214f, 0.076185f, 0.078187f, 0.080220f, 0.082283f, 0.084376f, 0.086500f, 0.088656f, 0.090842f, 0.093059f, 0.095307f, + 0.097587f, 0.099899f, 0.102242f, 0.104616f, 0.107023f, 0.109462f, 0.111932f, 0.114435f, 0.116971f, 0.119538f, 0.122139f, + 0.124772f, 0.127438f, 0.130136f, 0.132868f, 0.135633f, 0.138432f, 0.141263f, 0.144128f, 0.147027f, 0.149960f, 0.152926f, + 0.155926f, 0.158961f, 0.162029f, 0.165132f, 0.168269f, 0.171441f, 0.174647f, 0.177888f, 0.181164f, 0.184475f, 0.187821f, + 0.191202f, 0.194618f, 0.198069f, 0.201556f, 0.205079f, 0.208637f, 0.212231f, 0.215861f, 0.219526f, 0.223228f, 0.226966f, + 0.230740f, 0.234551f, 0.238398f, 0.242281f, 0.246201f, 0.250158f, 0.254152f, 0.258183f, 0.262251f, 0.266356f, 0.270498f, + 0.274677f, 0.278894f, 0.283149f, 0.287441f, 0.291771f, 0.296138f, 0.300544f, 0.304987f, 0.309469f, 0.313989f, 0.318547f, + 0.323143f, 0.327778f, 0.332452f, 0.337164f, 0.341914f, 0.346704f, 0.351533f, 0.356400f, 0.361307f, 0.366253f, 0.371238f, + 0.376262f, 0.381326f, 0.386430f, 0.391573f, 0.396755f, 0.401978f, 0.407240f, 0.412543f, 0.417885f, 0.423268f, 0.428691f, + 0.434154f, 0.439657f, 0.445201f, 0.450786f, 0.456411f, 0.462077f, 0.467784f, 0.473532f, 0.479320f, 0.485150f, 0.491021f, + 0.496933f, 0.502887f, 0.508881f, 0.514918f, 0.520996f, 0.527115f, 0.533276f, 0.539480f, 0.545725f, 0.552011f, 0.558340f, + 0.564712f, 0.571125f, 0.577581f, 0.584078f, 0.590619f, 0.597202f, 0.603827f, 0.610496f, 0.617207f, 0.623960f, 0.630757f, + 0.637597f, 0.644480f, 0.651406f, 0.658375f, 0.665387f, 0.672443f, 0.679543f, 0.686685f, 0.693872f, 0.701102f, 0.708376f, + 0.715694f, 0.723055f, 0.730461f, 0.737911f, 0.745404f, 0.752942f, 0.760525f, 0.768151f, 0.775822f, 0.783538f, 0.791298f, + 0.799103f, 0.806952f, 0.814847f, 0.822786f, 0.830770f, 0.838799f, 0.846873f, 0.854993f, 0.863157f, 0.871367f, 0.879622f, + 0.887923f, 0.896269f, 0.904661f, 0.913099f, 0.921582f, 0.930111f, 0.938686f, 0.947307f, 0.955974f, 0.964686f, 0.973445f, + 0.982251f, 0.991102f, 1.0f +}; + +typedef union +{ + unsigned int u; + float f; +} stbir__FP32; + +// From https://gist.github.com/rygorous/2203834 + +static const stbir_uint32 fp32_to_srgb8_tab4[104] = { + 0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 0x00a1000d, + 0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 0x00f4001a, 0x0101001a, + 0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 0x018f0033, 0x01a80033, 0x01c20033, + 0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067, + 0x037800ce, 0x03df00ce, 0x044600ce, 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5, + 0x06970158, 0x07420142, 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2, + 0x0b0f01cb, 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143, + 0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 0x182401af, + 0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 0x21520256, 0x227d0240, + 0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 0x2d1d0341, 0x2ebe031f, 0x304d0300, + 0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401, + 0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559, + 0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723, +}; + +static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) +{ + static const stbir__FP32 almostone = { 0x3f7fffff }; // 1-eps + static const stbir__FP32 minval = { (127-13) << 23 }; + stbir_uint32 tab,bias,scale,t; + stbir__FP32 f; + + // Clamp to [2^(-13), 1-eps]; these two values map to 0 and 1, respectively. + // The tests are carefully written so that NaNs map to 0, same as in the reference + // implementation. + if (!(in > minval.f)) // written this way to catch NaNs + return 0; + if (in > almostone.f) + return 255; + + // Do the table lookup and unpack bias, scale + f.f = in; + tab = fp32_to_srgb8_tab4[(f.u - minval.u) >> 20]; + bias = (tab >> 16) << 9; + scale = tab & 0xffff; + + // Grab next-highest mantissa bits and perform linear interpolation + t = (f.u >> 12) & 0xff; + return (unsigned char) ((bias + scale*t) >> 16); +} + +#ifndef STBIR_FORCE_GATHER_FILTER_SCANLINES_AMOUNT +#define STBIR_FORCE_GATHER_FILTER_SCANLINES_AMOUNT 32 // when downsampling and <= 32 scanlines of buffering, use gather. gather used down to 1/8th scaling for 25% win. +#endif + +#ifndef STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS +#define STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS 4 // when threading, what is the minimum number of scanlines for a split? +#endif + +// restrict pointers for the output pointers +#if defined( _MSC_VER ) && !defined(__clang__) + #define STBIR_STREAMOUT_PTR( star ) star __restrict + #define STBIR_NO_UNROLL( ptr ) __assume(ptr) // this oddly keeps msvc from unrolling a loop +#elif defined( __clang__ ) + #define STBIR_STREAMOUT_PTR( star ) star __restrict__ + #define STBIR_NO_UNROLL( ptr ) __asm__ (""::"r"(ptr)) +#elif defined( __GNUC__ ) + #define STBIR_STREAMOUT_PTR( star ) star __restrict__ + #define STBIR_NO_UNROLL( ptr ) __asm__ (""::"r"(ptr)) +#else + #define STBIR_STREAMOUT_PTR( star ) star + #define STBIR_NO_UNROLL( ptr ) +#endif + +#ifdef STBIR_NO_SIMD // force simd off for whatever reason + +// force simd off overrides everything else, so clear it all + +#ifdef STBIR_SSE2 +#undef STBIR_SSE2 +#endif + +#ifdef STBIR_AVX +#undef STBIR_AVX +#endif + +#ifdef STBIR_NEON +#undef STBIR_NEON +#endif + +#ifdef STBIR_AVX2 +#undef STBIR_AVX2 +#endif + +#ifdef STBIR_FP16C +#undef STBIR_FP16C +#endif + +#ifdef STBIR_WASM +#undef STBIR_WASM +#endif + +#ifdef STBIR_SIMD +#undef STBIR_SIMD +#endif + +#else // STBIR_SIMD + +#ifdef STBIR_SSE2 + #include + + #define stbir__simdf __m128 + #define stbir__simdi __m128i + + #define stbir_simdi_castf( reg ) _mm_castps_si128(reg) + #define stbir_simdf_casti( reg ) _mm_castsi128_ps(reg) + + #define stbir__simdf_load( reg, ptr ) (reg) = _mm_loadu_ps( (float const*)(ptr) ) + #define stbir__simdi_load( reg, ptr ) (reg) = _mm_loadu_si128 ( (stbir__simdi const*)(ptr) ) + #define stbir__simdf_load1( out, ptr ) (out) = _mm_load_ss( (float const*)(ptr) ) // top values can be random (not denormal or nan for perf) + #define stbir__simdi_load1( out, ptr ) (out) = _mm_castps_si128( _mm_load_ss( (float const*)(ptr) )) + #define stbir__simdf_load1z( out, ptr ) (out) = _mm_load_ss( (float const*)(ptr) ) // top values must be zero + #define stbir__simdf_frep4( fvar ) _mm_set_ps1( fvar ) + #define stbir__simdf_load1frep4( out, fvar ) (out) = _mm_set_ps1( fvar ) + #define stbir__simdf_load2( out, ptr ) (out) = _mm_castsi128_ps( _mm_loadl_epi64( (__m128i*)(ptr)) ) // top values can be random (not denormal or nan for perf) + #define stbir__simdf_load2z( out, ptr ) (out) = _mm_castsi128_ps( _mm_loadl_epi64( (__m128i*)(ptr)) ) // top values must be zero + #define stbir__simdf_load2hmerge( out, reg, ptr ) (out) = _mm_castpd_ps(_mm_loadh_pd( _mm_castps_pd(reg), (double*)(ptr) )) + + #define stbir__simdf_zeroP() _mm_setzero_ps() + #define stbir__simdf_zero( reg ) (reg) = _mm_setzero_ps() + + #define stbir__simdf_store( ptr, reg ) _mm_storeu_ps( (float*)(ptr), reg ) + #define stbir__simdf_store1( ptr, reg ) _mm_store_ss( (float*)(ptr), reg ) + #define stbir__simdf_store2( ptr, reg ) _mm_storel_epi64( (__m128i*)(ptr), _mm_castps_si128(reg) ) + #define stbir__simdf_store2h( ptr, reg ) _mm_storeh_pd( (double*)(ptr), _mm_castps_pd(reg) ) + + #define stbir__simdi_store( ptr, reg ) _mm_storeu_si128( (__m128i*)(ptr), reg ) + #define stbir__simdi_store1( ptr, reg ) _mm_store_ss( (float*)(ptr), _mm_castsi128_ps(reg) ) + #define stbir__simdi_store2( ptr, reg ) _mm_storel_epi64( (__m128i*)(ptr), (reg) ) + + #define stbir__prefetch( ptr ) _mm_prefetch((char*)(ptr), _MM_HINT_T0 ) + + #define stbir__simdi_expand_u8_to_u32(out0,out1,out2,out3,ireg) \ + { \ + stbir__simdi zero = _mm_setzero_si128(); \ + out2 = _mm_unpacklo_epi8( ireg, zero ); \ + out3 = _mm_unpackhi_epi8( ireg, zero ); \ + out0 = _mm_unpacklo_epi16( out2, zero ); \ + out1 = _mm_unpackhi_epi16( out2, zero ); \ + out2 = _mm_unpacklo_epi16( out3, zero ); \ + out3 = _mm_unpackhi_epi16( out3, zero ); \ + } + +#define stbir__simdi_expand_u8_to_1u32(out,ireg) \ + { \ + stbir__simdi zero = _mm_setzero_si128(); \ + out = _mm_unpacklo_epi8( ireg, zero ); \ + out = _mm_unpacklo_epi16( out, zero ); \ + } + + #define stbir__simdi_expand_u16_to_u32(out0,out1,ireg) \ + { \ + stbir__simdi zero = _mm_setzero_si128(); \ + out0 = _mm_unpacklo_epi16( ireg, zero ); \ + out1 = _mm_unpackhi_epi16( ireg, zero ); \ + } + + #define stbir__simdf_convert_float_to_i32( i, f ) (i) = _mm_cvttps_epi32(f) + #define stbir__simdf_convert_float_to_int( f ) _mm_cvtt_ss2si(f) + #define stbir__simdf_convert_float_to_uint8( f ) ((unsigned char)_mm_cvtsi128_si32(_mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(f,STBIR__CONSTF(STBIR_max_uint8_as_float)),_mm_setzero_ps())))) + #define stbir__simdf_convert_float_to_short( f ) ((unsigned short)_mm_cvtsi128_si32(_mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(f,STBIR__CONSTF(STBIR_max_uint16_as_float)),_mm_setzero_ps())))) + + #define stbir__simdi_to_int( i ) _mm_cvtsi128_si32(i) + #define stbir__simdi_convert_i32_to_float(out, ireg) (out) = _mm_cvtepi32_ps( ireg ) + #define stbir__simdf_add( out, reg0, reg1 ) (out) = _mm_add_ps( reg0, reg1 ) + #define stbir__simdf_mult( out, reg0, reg1 ) (out) = _mm_mul_ps( reg0, reg1 ) + #define stbir__simdf_mult_mem( out, reg, ptr ) (out) = _mm_mul_ps( reg, _mm_loadu_ps( (float const*)(ptr) ) ) + #define stbir__simdf_mult1_mem( out, reg, ptr ) (out) = _mm_mul_ss( reg, _mm_load_ss( (float const*)(ptr) ) ) + #define stbir__simdf_add_mem( out, reg, ptr ) (out) = _mm_add_ps( reg, _mm_loadu_ps( (float const*)(ptr) ) ) + #define stbir__simdf_add1_mem( out, reg, ptr ) (out) = _mm_add_ss( reg, _mm_load_ss( (float const*)(ptr) ) ) + + #ifdef STBIR_USE_FMA // not on by default to maintain bit identical simd to non-simd + #include + #define stbir__simdf_madd( out, add, mul1, mul2 ) (out) = _mm_fmadd_ps( mul1, mul2, add ) + #define stbir__simdf_madd1( out, add, mul1, mul2 ) (out) = _mm_fmadd_ss( mul1, mul2, add ) + #define stbir__simdf_madd_mem( out, add, mul, ptr ) (out) = _mm_fmadd_ps( mul, _mm_loadu_ps( (float const*)(ptr) ), add ) + #define stbir__simdf_madd1_mem( out, add, mul, ptr ) (out) = _mm_fmadd_ss( mul, _mm_load_ss( (float const*)(ptr) ), add ) + #else + #define stbir__simdf_madd( out, add, mul1, mul2 ) (out) = _mm_add_ps( add, _mm_mul_ps( mul1, mul2 ) ) + #define stbir__simdf_madd1( out, add, mul1, mul2 ) (out) = _mm_add_ss( add, _mm_mul_ss( mul1, mul2 ) ) + #define stbir__simdf_madd_mem( out, add, mul, ptr ) (out) = _mm_add_ps( add, _mm_mul_ps( mul, _mm_loadu_ps( (float const*)(ptr) ) ) ) + #define stbir__simdf_madd1_mem( out, add, mul, ptr ) (out) = _mm_add_ss( add, _mm_mul_ss( mul, _mm_load_ss( (float const*)(ptr) ) ) ) + #endif + + #define stbir__simdf_add1( out, reg0, reg1 ) (out) = _mm_add_ss( reg0, reg1 ) + #define stbir__simdf_mult1( out, reg0, reg1 ) (out) = _mm_mul_ss( reg0, reg1 ) + + #define stbir__simdf_and( out, reg0, reg1 ) (out) = _mm_and_ps( reg0, reg1 ) + #define stbir__simdf_or( out, reg0, reg1 ) (out) = _mm_or_ps( reg0, reg1 ) + + #define stbir__simdf_min( out, reg0, reg1 ) (out) = _mm_min_ps( reg0, reg1 ) + #define stbir__simdf_max( out, reg0, reg1 ) (out) = _mm_max_ps( reg0, reg1 ) + #define stbir__simdf_min1( out, reg0, reg1 ) (out) = _mm_min_ss( reg0, reg1 ) + #define stbir__simdf_max1( out, reg0, reg1 ) (out) = _mm_max_ss( reg0, reg1 ) + + #define stbir__simdf_0123ABCDto3ABx( out, reg0, reg1 ) (out)=_mm_castsi128_ps( _mm_shuffle_epi32( _mm_castps_si128( _mm_shuffle_ps( reg1,reg0, (0<<0) + (1<<2) + (2<<4) + (3<<6) )), (3<<0) + (0<<2) + (1<<4) + (2<<6) ) ) + #define stbir__simdf_0123ABCDto23Ax( out, reg0, reg1 ) (out)=_mm_castsi128_ps( _mm_shuffle_epi32( _mm_castps_si128( _mm_shuffle_ps( reg1,reg0, (0<<0) + (1<<2) + (2<<4) + (3<<6) )), (2<<0) + (3<<2) + (0<<4) + (1<<6) ) ) + + static const stbir__simdf STBIR_zeroones = { 0.0f,1.0f,0.0f,1.0f }; + static const stbir__simdf STBIR_onezeros = { 1.0f,0.0f,1.0f,0.0f }; + #define stbir__simdf_aaa1( out, alp, ones ) (out)=_mm_castsi128_ps( _mm_shuffle_epi32( _mm_castps_si128( _mm_movehl_ps( ones, alp ) ), (1<<0) + (1<<2) + (1<<4) + (2<<6) ) ) + #define stbir__simdf_1aaa( out, alp, ones ) (out)=_mm_castsi128_ps( _mm_shuffle_epi32( _mm_castps_si128( _mm_movelh_ps( ones, alp ) ), (0<<0) + (2<<2) + (2<<4) + (2<<6) ) ) + #define stbir__simdf_a1a1( out, alp, ones) (out) = _mm_or_ps( _mm_castsi128_ps( _mm_srli_epi64( _mm_castps_si128(alp), 32 ) ), STBIR_zeroones ) + #define stbir__simdf_1a1a( out, alp, ones) (out) = _mm_or_ps( _mm_castsi128_ps( _mm_slli_epi64( _mm_castps_si128(alp), 32 ) ), STBIR_onezeros ) + + #define stbir__simdf_swiz( reg, one, two, three, four ) _mm_castsi128_ps( _mm_shuffle_epi32( _mm_castps_si128( reg ), (one<<0) + (two<<2) + (three<<4) + (four<<6) ) ) + + #define stbir__simdi_and( out, reg0, reg1 ) (out) = _mm_and_si128( reg0, reg1 ) + #define stbir__simdi_or( out, reg0, reg1 ) (out) = _mm_or_si128( reg0, reg1 ) + #define stbir__simdi_16madd( out, reg0, reg1 ) (out) = _mm_madd_epi16( reg0, reg1 ) + + #define stbir__simdf_pack_to_8bytes(out,aa,bb) \ + { \ + stbir__simdf af,bf; \ + stbir__simdi a,b; \ + af = _mm_min_ps( aa, STBIR_max_uint8_as_float ); \ + bf = _mm_min_ps( bb, STBIR_max_uint8_as_float ); \ + af = _mm_max_ps( af, _mm_setzero_ps() ); \ + bf = _mm_max_ps( bf, _mm_setzero_ps() ); \ + a = _mm_cvttps_epi32( af ); \ + b = _mm_cvttps_epi32( bf ); \ + a = _mm_packs_epi32( a, b ); \ + out = _mm_packus_epi16( a, a ); \ + } + + #define stbir__simdf_load4_transposed( o0, o1, o2, o3, ptr ) \ + stbir__simdf_load( o0, (ptr) ); \ + stbir__simdf_load( o1, (ptr)+4 ); \ + stbir__simdf_load( o2, (ptr)+8 ); \ + stbir__simdf_load( o3, (ptr)+12 ); \ + { \ + __m128 tmp0, tmp1, tmp2, tmp3; \ + tmp0 = _mm_unpacklo_ps(o0, o1); \ + tmp2 = _mm_unpacklo_ps(o2, o3); \ + tmp1 = _mm_unpackhi_ps(o0, o1); \ + tmp3 = _mm_unpackhi_ps(o2, o3); \ + o0 = _mm_movelh_ps(tmp0, tmp2); \ + o1 = _mm_movehl_ps(tmp2, tmp0); \ + o2 = _mm_movelh_ps(tmp1, tmp3); \ + o3 = _mm_movehl_ps(tmp3, tmp1); \ + } + + #define stbir__interleave_pack_and_store_16_u8( ptr, r0, r1, r2, r3 ) \ + r0 = _mm_packs_epi32( r0, r1 ); \ + r2 = _mm_packs_epi32( r2, r3 ); \ + r1 = _mm_unpacklo_epi16( r0, r2 ); \ + r3 = _mm_unpackhi_epi16( r0, r2 ); \ + r0 = _mm_unpacklo_epi16( r1, r3 ); \ + r2 = _mm_unpackhi_epi16( r1, r3 ); \ + r0 = _mm_packus_epi16( r0, r2 ); \ + stbir__simdi_store( ptr, r0 ); \ + + #define stbir__simdi_32shr( out, reg, imm ) out = _mm_srli_epi32( reg, imm ) + + #if defined(_MSC_VER) && !defined(__clang__) + // msvc inits with 8 bytes + #define STBIR__CONST_32_TO_8( v ) (char)(unsigned char)((v)&255),(char)(unsigned char)(((v)>>8)&255),(char)(unsigned char)(((v)>>16)&255),(char)(unsigned char)(((v)>>24)&255) + #define STBIR__CONST_4_32i( v ) STBIR__CONST_32_TO_8( v ), STBIR__CONST_32_TO_8( v ), STBIR__CONST_32_TO_8( v ), STBIR__CONST_32_TO_8( v ) + #define STBIR__CONST_4d_32i( v0, v1, v2, v3 ) STBIR__CONST_32_TO_8( v0 ), STBIR__CONST_32_TO_8( v1 ), STBIR__CONST_32_TO_8( v2 ), STBIR__CONST_32_TO_8( v3 ) + #else + // everything else inits with long long's + #define STBIR__CONST_4_32i( v ) (long long)((((stbir_uint64)(stbir_uint32)(v))<<32)|((stbir_uint64)(stbir_uint32)(v))),(long long)((((stbir_uint64)(stbir_uint32)(v))<<32)|((stbir_uint64)(stbir_uint32)(v))) + #define STBIR__CONST_4d_32i( v0, v1, v2, v3 ) (long long)((((stbir_uint64)(stbir_uint32)(v1))<<32)|((stbir_uint64)(stbir_uint32)(v0))),(long long)((((stbir_uint64)(stbir_uint32)(v3))<<32)|((stbir_uint64)(stbir_uint32)(v2))) + #endif + + #define STBIR__SIMDF_CONST(var, x) stbir__simdf var = { x, x, x, x } + #define STBIR__SIMDI_CONST(var, x) stbir__simdi var = { STBIR__CONST_4_32i(x) } + #define STBIR__CONSTF(var) (var) + #define STBIR__CONSTI(var) (var) + + #if defined(STBIR_AVX) || defined(__SSE4_1__) + #include + #define stbir__simdf_pack_to_8words(out,reg0,reg1) out = _mm_packus_epi32(_mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(reg0,STBIR__CONSTF(STBIR_max_uint16_as_float)),_mm_setzero_ps())), _mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(reg1,STBIR__CONSTF(STBIR_max_uint16_as_float)),_mm_setzero_ps()))) + #else + STBIR__SIMDI_CONST(stbir__s32_32768, 32768); + STBIR__SIMDI_CONST(stbir__s16_32768, ((32768<<16)|32768)); + + #define stbir__simdf_pack_to_8words(out,reg0,reg1) \ + { \ + stbir__simdi tmp0,tmp1; \ + tmp0 = _mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(reg0,STBIR__CONSTF(STBIR_max_uint16_as_float)),_mm_setzero_ps())); \ + tmp1 = _mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(reg1,STBIR__CONSTF(STBIR_max_uint16_as_float)),_mm_setzero_ps())); \ + tmp0 = _mm_sub_epi32( tmp0, stbir__s32_32768 ); \ + tmp1 = _mm_sub_epi32( tmp1, stbir__s32_32768 ); \ + out = _mm_packs_epi32( tmp0, tmp1 ); \ + out = _mm_sub_epi16( out, stbir__s16_32768 ); \ + } + + #endif + + #define STBIR_SIMD + + // if we detect AVX, set the simd8 defines + #ifdef STBIR_AVX + #include + #define STBIR_SIMD8 + #define stbir__simdf8 __m256 + #define stbir__simdi8 __m256i + #define stbir__simdf8_load( out, ptr ) (out) = _mm256_loadu_ps( (float const *)(ptr) ) + #define stbir__simdi8_load( out, ptr ) (out) = _mm256_loadu_si256( (__m256i const *)(ptr) ) + #define stbir__simdf8_mult( out, a, b ) (out) = _mm256_mul_ps( (a), (b) ) + #define stbir__simdf8_store( ptr, out ) _mm256_storeu_ps( (float*)(ptr), out ) + #define stbir__simdi8_store( ptr, reg ) _mm256_storeu_si256( (__m256i*)(ptr), reg ) + #define stbir__simdf8_frep8( fval ) _mm256_set1_ps( fval ) + + #define stbir__simdf8_min( out, reg0, reg1 ) (out) = _mm256_min_ps( reg0, reg1 ) + #define stbir__simdf8_max( out, reg0, reg1 ) (out) = _mm256_max_ps( reg0, reg1 ) + + #define stbir__simdf8_add4halves( out, bot4, top8 ) (out) = _mm_add_ps( bot4, _mm256_extractf128_ps( top8, 1 ) ) + #define stbir__simdf8_mult_mem( out, reg, ptr ) (out) = _mm256_mul_ps( reg, _mm256_loadu_ps( (float const*)(ptr) ) ) + #define stbir__simdf8_add_mem( out, reg, ptr ) (out) = _mm256_add_ps( reg, _mm256_loadu_ps( (float const*)(ptr) ) ) + #define stbir__simdf8_add( out, a, b ) (out) = _mm256_add_ps( a, b ) + #define stbir__simdf8_load1b( out, ptr ) (out) = _mm256_broadcast_ss( ptr ) + #define stbir__simdf_load1rep4( out, ptr ) (out) = _mm_broadcast_ss( ptr ) // avx load instruction + + #define stbir__simdi8_convert_i32_to_float(out, ireg) (out) = _mm256_cvtepi32_ps( ireg ) + #define stbir__simdf8_convert_float_to_i32( i, f ) (i) = _mm256_cvttps_epi32(f) + + #define stbir__simdf8_bot4s( out, a, b ) (out) = _mm256_permute2f128_ps(a,b, (0<<0)+(2<<4) ) + #define stbir__simdf8_top4s( out, a, b ) (out) = _mm256_permute2f128_ps(a,b, (1<<0)+(3<<4) ) + + #define stbir__simdf8_gettop4( reg ) _mm256_extractf128_ps(reg,1) + + #ifdef STBIR_AVX2 + + #define stbir__simdi8_expand_u8_to_u32(out0,out1,ireg) \ + { \ + stbir__simdi8 a, zero =_mm256_setzero_si256();\ + a = _mm256_permute4x64_epi64( _mm256_unpacklo_epi8( _mm256_permute4x64_epi64(_mm256_castsi128_si256(ireg),(0<<0)+(2<<2)+(1<<4)+(3<<6)), zero ),(0<<0)+(2<<2)+(1<<4)+(3<<6)); \ + out0 = _mm256_unpacklo_epi16( a, zero ); \ + out1 = _mm256_unpackhi_epi16( a, zero ); \ + } + + #define stbir__simdf8_pack_to_16bytes(out,aa,bb) \ + { \ + stbir__simdi8 t; \ + stbir__simdf8 af,bf; \ + stbir__simdi8 a,b; \ + af = _mm256_min_ps( aa, STBIR_max_uint8_as_floatX ); \ + bf = _mm256_min_ps( bb, STBIR_max_uint8_as_floatX ); \ + af = _mm256_max_ps( af, _mm256_setzero_ps() ); \ + bf = _mm256_max_ps( bf, _mm256_setzero_ps() ); \ + a = _mm256_cvttps_epi32( af ); \ + b = _mm256_cvttps_epi32( bf ); \ + t = _mm256_permute4x64_epi64( _mm256_packs_epi32( a, b ), (0<<0)+(2<<2)+(1<<4)+(3<<6) ); \ + out = _mm256_castsi256_si128( _mm256_permute4x64_epi64( _mm256_packus_epi16( t, t ), (0<<0)+(2<<2)+(1<<4)+(3<<6) ) ); \ + } + + #define stbir__simdi8_expand_u16_to_u32(out,ireg) out = _mm256_unpacklo_epi16( _mm256_permute4x64_epi64(_mm256_castsi128_si256(ireg),(0<<0)+(2<<2)+(1<<4)+(3<<6)), _mm256_setzero_si256() ); + + #define stbir__simdf8_pack_to_16words(out,aa,bb) \ + { \ + stbir__simdf8 af,bf; \ + stbir__simdi8 a,b; \ + af = _mm256_min_ps( aa, STBIR_max_uint16_as_floatX ); \ + bf = _mm256_min_ps( bb, STBIR_max_uint16_as_floatX ); \ + af = _mm256_max_ps( af, _mm256_setzero_ps() ); \ + bf = _mm256_max_ps( bf, _mm256_setzero_ps() ); \ + a = _mm256_cvttps_epi32( af ); \ + b = _mm256_cvttps_epi32( bf ); \ + (out) = _mm256_permute4x64_epi64( _mm256_packus_epi32(a, b), (0<<0)+(2<<2)+(1<<4)+(3<<6) ); \ + } + + #else + + #define stbir__simdi8_expand_u8_to_u32(out0,out1,ireg) \ + { \ + stbir__simdi a,zero = _mm_setzero_si128(); \ + a = _mm_unpacklo_epi8( ireg, zero ); \ + out0 = _mm256_setr_m128i( _mm_unpacklo_epi16( a, zero ), _mm_unpackhi_epi16( a, zero ) ); \ + a = _mm_unpackhi_epi8( ireg, zero ); \ + out1 = _mm256_setr_m128i( _mm_unpacklo_epi16( a, zero ), _mm_unpackhi_epi16( a, zero ) ); \ + } + + #define stbir__simdf8_pack_to_16bytes(out,aa,bb) \ + { \ + stbir__simdi t; \ + stbir__simdf8 af,bf; \ + stbir__simdi8 a,b; \ + af = _mm256_min_ps( aa, STBIR_max_uint8_as_floatX ); \ + bf = _mm256_min_ps( bb, STBIR_max_uint8_as_floatX ); \ + af = _mm256_max_ps( af, _mm256_setzero_ps() ); \ + bf = _mm256_max_ps( bf, _mm256_setzero_ps() ); \ + a = _mm256_cvttps_epi32( af ); \ + b = _mm256_cvttps_epi32( bf ); \ + out = _mm_packs_epi32( _mm256_castsi256_si128(a), _mm256_extractf128_si256( a, 1 ) ); \ + out = _mm_packus_epi16( out, out ); \ + t = _mm_packs_epi32( _mm256_castsi256_si128(b), _mm256_extractf128_si256( b, 1 ) ); \ + t = _mm_packus_epi16( t, t ); \ + out = _mm_castps_si128( _mm_shuffle_ps( _mm_castsi128_ps(out), _mm_castsi128_ps(t), (0<<0)+(1<<2)+(0<<4)+(1<<6) ) ); \ + } + + #define stbir__simdi8_expand_u16_to_u32(out,ireg) \ + { \ + stbir__simdi a,b,zero = _mm_setzero_si128(); \ + a = _mm_unpacklo_epi16( ireg, zero ); \ + b = _mm_unpackhi_epi16( ireg, zero ); \ + out = _mm256_insertf128_si256( _mm256_castsi128_si256( a ), b, 1 ); \ + } + + #define stbir__simdf8_pack_to_16words(out,aa,bb) \ + { \ + stbir__simdi t0,t1; \ + stbir__simdf8 af,bf; \ + stbir__simdi8 a,b; \ + af = _mm256_min_ps( aa, STBIR_max_uint16_as_floatX ); \ + bf = _mm256_min_ps( bb, STBIR_max_uint16_as_floatX ); \ + af = _mm256_max_ps( af, _mm256_setzero_ps() ); \ + bf = _mm256_max_ps( bf, _mm256_setzero_ps() ); \ + a = _mm256_cvttps_epi32( af ); \ + b = _mm256_cvttps_epi32( bf ); \ + t0 = _mm_packus_epi32( _mm256_castsi256_si128(a), _mm256_extractf128_si256( a, 1 ) ); \ + t1 = _mm_packus_epi32( _mm256_castsi256_si128(b), _mm256_extractf128_si256( b, 1 ) ); \ + out = _mm256_setr_m128i( t0, t1 ); \ + } + + #endif + + static __m256i stbir_00001111 = { STBIR__CONST_4d_32i( 0, 0, 0, 0 ), STBIR__CONST_4d_32i( 1, 1, 1, 1 ) }; + #define stbir__simdf8_0123to00001111( out, in ) (out) = _mm256_permutevar_ps ( in, stbir_00001111 ) + + static __m256i stbir_22223333 = { STBIR__CONST_4d_32i( 2, 2, 2, 2 ), STBIR__CONST_4d_32i( 3, 3, 3, 3 ) }; + #define stbir__simdf8_0123to22223333( out, in ) (out) = _mm256_permutevar_ps ( in, stbir_22223333 ) + + #define stbir__simdf8_0123to2222( out, in ) (out) = stbir__simdf_swiz(_mm256_castps256_ps128(in), 2,2,2,2 ) + + #define stbir__simdf8_load4b( out, ptr ) (out) = _mm256_broadcast_ps( (__m128 const *)(ptr) ) + + static __m256i stbir_00112233 = { STBIR__CONST_4d_32i( 0, 0, 1, 1 ), STBIR__CONST_4d_32i( 2, 2, 3, 3 ) }; + #define stbir__simdf8_0123to00112233( out, in ) (out) = _mm256_permutevar_ps ( in, stbir_00112233 ) + #define stbir__simdf8_add4( out, a8, b ) (out) = _mm256_add_ps( a8, _mm256_castps128_ps256( b ) ) + + static __m256i stbir_load6 = { STBIR__CONST_4_32i( 0x80000000 ), STBIR__CONST_4d_32i( 0x80000000, 0x80000000, 0, 0 ) }; + #define stbir__simdf8_load6z( out, ptr ) (out) = _mm256_maskload_ps( ptr, stbir_load6 ) + + #define stbir__simdf8_0123to00000000( out, in ) (out) = _mm256_shuffle_ps ( in, in, (0<<0)+(0<<2)+(0<<4)+(0<<6) ) + #define stbir__simdf8_0123to11111111( out, in ) (out) = _mm256_shuffle_ps ( in, in, (1<<0)+(1<<2)+(1<<4)+(1<<6) ) + #define stbir__simdf8_0123to22222222( out, in ) (out) = _mm256_shuffle_ps ( in, in, (2<<0)+(2<<2)+(2<<4)+(2<<6) ) + #define stbir__simdf8_0123to33333333( out, in ) (out) = _mm256_shuffle_ps ( in, in, (3<<0)+(3<<2)+(3<<4)+(3<<6) ) + #define stbir__simdf8_0123to21032103( out, in ) (out) = _mm256_shuffle_ps ( in, in, (2<<0)+(1<<2)+(0<<4)+(3<<6) ) + #define stbir__simdf8_0123to32103210( out, in ) (out) = _mm256_shuffle_ps ( in, in, (3<<0)+(2<<2)+(1<<4)+(0<<6) ) + #define stbir__simdf8_0123to12301230( out, in ) (out) = _mm256_shuffle_ps ( in, in, (1<<0)+(2<<2)+(3<<4)+(0<<6) ) + #define stbir__simdf8_0123to10321032( out, in ) (out) = _mm256_shuffle_ps ( in, in, (1<<0)+(0<<2)+(3<<4)+(2<<6) ) + #define stbir__simdf8_0123to30123012( out, in ) (out) = _mm256_shuffle_ps ( in, in, (3<<0)+(0<<2)+(1<<4)+(2<<6) ) + + #define stbir__simdf8_0123to11331133( out, in ) (out) = _mm256_shuffle_ps ( in, in, (1<<0)+(1<<2)+(3<<4)+(3<<6) ) + #define stbir__simdf8_0123to00220022( out, in ) (out) = _mm256_shuffle_ps ( in, in, (0<<0)+(0<<2)+(2<<4)+(2<<6) ) + + #define stbir__simdf8_aaa1( out, alp, ones ) (out) = _mm256_blend_ps( alp, ones, (1<<0)+(1<<1)+(1<<2)+(0<<3)+(1<<4)+(1<<5)+(1<<6)+(0<<7)); (out)=_mm256_shuffle_ps( out,out, (3<<0) + (3<<2) + (3<<4) + (0<<6) ) + #define stbir__simdf8_1aaa( out, alp, ones ) (out) = _mm256_blend_ps( alp, ones, (0<<0)+(1<<1)+(1<<2)+(1<<3)+(0<<4)+(1<<5)+(1<<6)+(1<<7)); (out)=_mm256_shuffle_ps( out,out, (1<<0) + (0<<2) + (0<<4) + (0<<6) ) + #define stbir__simdf8_a1a1( out, alp, ones) (out) = _mm256_blend_ps( alp, ones, (1<<0)+(0<<1)+(1<<2)+(0<<3)+(1<<4)+(0<<5)+(1<<6)+(0<<7)); (out)=_mm256_shuffle_ps( out,out, (1<<0) + (0<<2) + (3<<4) + (2<<6) ) + #define stbir__simdf8_1a1a( out, alp, ones) (out) = _mm256_blend_ps( alp, ones, (0<<0)+(1<<1)+(0<<2)+(1<<3)+(0<<4)+(1<<5)+(0<<6)+(1<<7)); (out)=_mm256_shuffle_ps( out,out, (1<<0) + (0<<2) + (3<<4) + (2<<6) ) + + #define stbir__simdf8_zero( reg ) (reg) = _mm256_setzero_ps() + + #ifdef STBIR_USE_FMA // not on by default to maintain bit identical simd to non-simd + #define stbir__simdf8_madd( out, add, mul1, mul2 ) (out) = _mm256_fmadd_ps( mul1, mul2, add ) + #define stbir__simdf8_madd_mem( out, add, mul, ptr ) (out) = _mm256_fmadd_ps( mul, _mm256_loadu_ps( (float const*)(ptr) ), add ) + #define stbir__simdf8_madd_mem4( out, add, mul, ptr )(out) = _mm256_fmadd_ps( _mm256_setr_m128( mul, _mm_setzero_ps() ), _mm256_setr_m128( _mm_loadu_ps( (float const*)(ptr) ), _mm_setzero_ps() ), add ) + #else + #define stbir__simdf8_madd( out, add, mul1, mul2 ) (out) = _mm256_add_ps( add, _mm256_mul_ps( mul1, mul2 ) ) + #define stbir__simdf8_madd_mem( out, add, mul, ptr ) (out) = _mm256_add_ps( add, _mm256_mul_ps( mul, _mm256_loadu_ps( (float const*)(ptr) ) ) ) + #define stbir__simdf8_madd_mem4( out, add, mul, ptr ) (out) = _mm256_add_ps( add, _mm256_setr_m128( _mm_mul_ps( mul, _mm_loadu_ps( (float const*)(ptr) ) ), _mm_setzero_ps() ) ) + #endif + #define stbir__if_simdf8_cast_to_simdf4( val ) _mm256_castps256_ps128( val ) + + #endif + + #ifdef STBIR_FLOORF + #undef STBIR_FLOORF + #endif + #define STBIR_FLOORF stbir_simd_floorf + static stbir__inline float stbir_simd_floorf(float x) // martins floorf + { + #if defined(STBIR_AVX) || defined(__SSE4_1__) || defined(STBIR_SSE41) + __m128 t = _mm_set_ss(x); + return _mm_cvtss_f32( _mm_floor_ss(t, t) ); + #else + __m128 f = _mm_set_ss(x); + __m128 t = _mm_cvtepi32_ps(_mm_cvttps_epi32(f)); + __m128 r = _mm_add_ss(t, _mm_and_ps(_mm_cmplt_ss(f, t), _mm_set_ss(-1.0f))); + return _mm_cvtss_f32(r); + #endif + } + + #ifdef STBIR_CEILF + #undef STBIR_CEILF + #endif + #define STBIR_CEILF stbir_simd_ceilf + static stbir__inline float stbir_simd_ceilf(float x) // martins ceilf + { + #if defined(STBIR_AVX) || defined(__SSE4_1__) || defined(STBIR_SSE41) + __m128 t = _mm_set_ss(x); + return _mm_cvtss_f32( _mm_ceil_ss(t, t) ); + #else + __m128 f = _mm_set_ss(x); + __m128 t = _mm_cvtepi32_ps(_mm_cvttps_epi32(f)); + __m128 r = _mm_add_ss(t, _mm_and_ps(_mm_cmplt_ss(t, f), _mm_set_ss(1.0f))); + return _mm_cvtss_f32(r); + #endif + } + +#elif defined(STBIR_NEON) + + #include + + #define stbir__simdf float32x4_t + #define stbir__simdi uint32x4_t + + #define stbir_simdi_castf( reg ) vreinterpretq_u32_f32(reg) + #define stbir_simdf_casti( reg ) vreinterpretq_f32_u32(reg) + + #define stbir__simdf_load( reg, ptr ) (reg) = vld1q_f32( (float const*)(ptr) ) + #define stbir__simdi_load( reg, ptr ) (reg) = vld1q_u32( (uint32_t const*)(ptr) ) + #define stbir__simdf_load1( out, ptr ) (out) = vld1q_dup_f32( (float const*)(ptr) ) // top values can be random (not denormal or nan for perf) + #define stbir__simdi_load1( out, ptr ) (out) = vld1q_dup_u32( (uint32_t const*)(ptr) ) + #define stbir__simdf_load1z( out, ptr ) (out) = vld1q_lane_f32( (float const*)(ptr), vdupq_n_f32(0), 0 ) // top values must be zero + #define stbir__simdf_frep4( fvar ) vdupq_n_f32( fvar ) + #define stbir__simdf_load1frep4( out, fvar ) (out) = vdupq_n_f32( fvar ) + #define stbir__simdf_load2( out, ptr ) (out) = vcombine_f32( vld1_f32( (float const*)(ptr) ), vcreate_f32(0) ) // top values can be random (not denormal or nan for perf) + #define stbir__simdf_load2z( out, ptr ) (out) = vcombine_f32( vld1_f32( (float const*)(ptr) ), vcreate_f32(0) ) // top values must be zero + #define stbir__simdf_load2hmerge( out, reg, ptr ) (out) = vcombine_f32( vget_low_f32(reg), vld1_f32( (float const*)(ptr) ) ) + + #define stbir__simdf_zeroP() vdupq_n_f32(0) + #define stbir__simdf_zero( reg ) (reg) = vdupq_n_f32(0) + + #define stbir__simdf_store( ptr, reg ) vst1q_f32( (float*)(ptr), reg ) + #define stbir__simdf_store1( ptr, reg ) vst1q_lane_f32( (float*)(ptr), reg, 0) + #define stbir__simdf_store2( ptr, reg ) vst1_f32( (float*)(ptr), vget_low_f32(reg) ) + #define stbir__simdf_store2h( ptr, reg ) vst1_f32( (float*)(ptr), vget_high_f32(reg) ) + + #define stbir__simdi_store( ptr, reg ) vst1q_u32( (uint32_t*)(ptr), reg ) + #define stbir__simdi_store1( ptr, reg ) vst1q_lane_u32( (uint32_t*)(ptr), reg, 0 ) + #define stbir__simdi_store2( ptr, reg ) vst1_u32( (uint32_t*)(ptr), vget_low_u32(reg) ) + + #define stbir__prefetch( ptr ) + + #define stbir__simdi_expand_u8_to_u32(out0,out1,out2,out3,ireg) \ + { \ + uint16x8_t l = vmovl_u8( vget_low_u8 ( vreinterpretq_u8_u32(ireg) ) ); \ + uint16x8_t h = vmovl_u8( vget_high_u8( vreinterpretq_u8_u32(ireg) ) ); \ + out0 = vmovl_u16( vget_low_u16 ( l ) ); \ + out1 = vmovl_u16( vget_high_u16( l ) ); \ + out2 = vmovl_u16( vget_low_u16 ( h ) ); \ + out3 = vmovl_u16( vget_high_u16( h ) ); \ + } + + #define stbir__simdi_expand_u8_to_1u32(out,ireg) \ + { \ + uint16x8_t tmp = vmovl_u8( vget_low_u8( vreinterpretq_u8_u32(ireg) ) ); \ + out = vmovl_u16( vget_low_u16( tmp ) ); \ + } + + #define stbir__simdi_expand_u16_to_u32(out0,out1,ireg) \ + { \ + uint16x8_t tmp = vreinterpretq_u16_u32(ireg); \ + out0 = vmovl_u16( vget_low_u16 ( tmp ) ); \ + out1 = vmovl_u16( vget_high_u16( tmp ) ); \ + } + + #define stbir__simdf_convert_float_to_i32( i, f ) (i) = vreinterpretq_u32_s32( vcvtq_s32_f32(f) ) + #define stbir__simdf_convert_float_to_int( f ) vgetq_lane_s32(vcvtq_s32_f32(f), 0) + #define stbir__simdi_to_int( i ) (int)vgetq_lane_u32(i, 0) + #define stbir__simdf_convert_float_to_uint8( f ) ((unsigned char)vgetq_lane_s32(vcvtq_s32_f32(vmaxq_f32(vminq_f32(f,STBIR__CONSTF(STBIR_max_uint8_as_float)),vdupq_n_f32(0))), 0)) + #define stbir__simdf_convert_float_to_short( f ) ((unsigned short)vgetq_lane_s32(vcvtq_s32_f32(vmaxq_f32(vminq_f32(f,STBIR__CONSTF(STBIR_max_uint16_as_float)),vdupq_n_f32(0))), 0)) + #define stbir__simdi_convert_i32_to_float(out, ireg) (out) = vcvtq_f32_s32( vreinterpretq_s32_u32(ireg) ) + #define stbir__simdf_add( out, reg0, reg1 ) (out) = vaddq_f32( reg0, reg1 ) + #define stbir__simdf_mult( out, reg0, reg1 ) (out) = vmulq_f32( reg0, reg1 ) + #define stbir__simdf_mult_mem( out, reg, ptr ) (out) = vmulq_f32( reg, vld1q_f32( (float const*)(ptr) ) ) + #define stbir__simdf_mult1_mem( out, reg, ptr ) (out) = vmulq_f32( reg, vld1q_dup_f32( (float const*)(ptr) ) ) + #define stbir__simdf_add_mem( out, reg, ptr ) (out) = vaddq_f32( reg, vld1q_f32( (float const*)(ptr) ) ) + #define stbir__simdf_add1_mem( out, reg, ptr ) (out) = vaddq_f32( reg, vld1q_dup_f32( (float const*)(ptr) ) ) + + #ifdef STBIR_USE_FMA // not on by default to maintain bit identical simd to non-simd (and also x64 no madd to arm madd) + #define stbir__simdf_madd( out, add, mul1, mul2 ) (out) = vfmaq_f32( add, mul1, mul2 ) + #define stbir__simdf_madd1( out, add, mul1, mul2 ) (out) = vfmaq_f32( add, mul1, mul2 ) + #define stbir__simdf_madd_mem( out, add, mul, ptr ) (out) = vfmaq_f32( add, mul, vld1q_f32( (float const*)(ptr) ) ) + #define stbir__simdf_madd1_mem( out, add, mul, ptr ) (out) = vfmaq_f32( add, mul, vld1q_dup_f32( (float const*)(ptr) ) ) + #else + #define stbir__simdf_madd( out, add, mul1, mul2 ) (out) = vaddq_f32( add, vmulq_f32( mul1, mul2 ) ) + #define stbir__simdf_madd1( out, add, mul1, mul2 ) (out) = vaddq_f32( add, vmulq_f32( mul1, mul2 ) ) + #define stbir__simdf_madd_mem( out, add, mul, ptr ) (out) = vaddq_f32( add, vmulq_f32( mul, vld1q_f32( (float const*)(ptr) ) ) ) + #define stbir__simdf_madd1_mem( out, add, mul, ptr ) (out) = vaddq_f32( add, vmulq_f32( mul, vld1q_dup_f32( (float const*)(ptr) ) ) ) + #endif + + #define stbir__simdf_add1( out, reg0, reg1 ) (out) = vaddq_f32( reg0, reg1 ) + #define stbir__simdf_mult1( out, reg0, reg1 ) (out) = vmulq_f32( reg0, reg1 ) + + #define stbir__simdf_and( out, reg0, reg1 ) (out) = vreinterpretq_f32_u32( vandq_u32( vreinterpretq_u32_f32(reg0), vreinterpretq_u32_f32(reg1) ) ) + #define stbir__simdf_or( out, reg0, reg1 ) (out) = vreinterpretq_f32_u32( vorrq_u32( vreinterpretq_u32_f32(reg0), vreinterpretq_u32_f32(reg1) ) ) + + #define stbir__simdf_min( out, reg0, reg1 ) (out) = vminq_f32( reg0, reg1 ) + #define stbir__simdf_max( out, reg0, reg1 ) (out) = vmaxq_f32( reg0, reg1 ) + #define stbir__simdf_min1( out, reg0, reg1 ) (out) = vminq_f32( reg0, reg1 ) + #define stbir__simdf_max1( out, reg0, reg1 ) (out) = vmaxq_f32( reg0, reg1 ) + + #define stbir__simdf_0123ABCDto3ABx( out, reg0, reg1 ) (out) = vextq_f32( reg0, reg1, 3 ) + #define stbir__simdf_0123ABCDto23Ax( out, reg0, reg1 ) (out) = vextq_f32( reg0, reg1, 2 ) + + #define stbir__simdf_a1a1( out, alp, ones ) (out) = vzipq_f32(vuzpq_f32(alp, alp).val[1], ones).val[0] + #define stbir__simdf_1a1a( out, alp, ones ) (out) = vzipq_f32(ones, vuzpq_f32(alp, alp).val[0]).val[0] + + #if defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) + + #define stbir__simdf_aaa1( out, alp, ones ) (out) = vcopyq_laneq_f32(vdupq_n_f32(vgetq_lane_f32(alp, 3)), 3, ones, 3) + #define stbir__simdf_1aaa( out, alp, ones ) (out) = vcopyq_laneq_f32(vdupq_n_f32(vgetq_lane_f32(alp, 0)), 0, ones, 0) + + #if defined( _MSC_VER ) && !defined(__clang__) + #define stbir_make16(a,b,c,d) vcombine_u8( \ + vcreate_u8( (4*a+0) | ((4*a+1)<<8) | ((4*a+2)<<16) | ((4*a+3)<<24) | \ + ((stbir_uint64)(4*b+0)<<32) | ((stbir_uint64)(4*b+1)<<40) | ((stbir_uint64)(4*b+2)<<48) | ((stbir_uint64)(4*b+3)<<56)), \ + vcreate_u8( (4*c+0) | ((4*c+1)<<8) | ((4*c+2)<<16) | ((4*c+3)<<24) | \ + ((stbir_uint64)(4*d+0)<<32) | ((stbir_uint64)(4*d+1)<<40) | ((stbir_uint64)(4*d+2)<<48) | ((stbir_uint64)(4*d+3)<<56) ) ) + #else + #define stbir_make16(a,b,c,d) (uint8x16_t){4*a+0,4*a+1,4*a+2,4*a+3,4*b+0,4*b+1,4*b+2,4*b+3,4*c+0,4*c+1,4*c+2,4*c+3,4*d+0,4*d+1,4*d+2,4*d+3} + #endif + + #define stbir__simdf_swiz( reg, one, two, three, four ) vreinterpretq_f32_u8( vqtbl1q_u8( vreinterpretq_u8_f32(reg), stbir_make16(one, two, three, four) ) ) + + #define stbir__simdi_16madd( out, reg0, reg1 ) \ + { \ + int16x8_t r0 = vreinterpretq_s16_u32(reg0); \ + int16x8_t r1 = vreinterpretq_s16_u32(reg1); \ + int32x4_t tmp0 = vmull_s16( vget_low_s16(r0), vget_low_s16(r1) ); \ + int32x4_t tmp1 = vmull_s16( vget_high_s16(r0), vget_high_s16(r1) ); \ + (out) = vreinterpretq_u32_s32( vpaddq_s32(tmp0, tmp1) ); \ + } + + #else + + #define stbir__simdf_aaa1( out, alp, ones ) (out) = vsetq_lane_f32(1.0f, vdupq_n_f32(vgetq_lane_f32(alp, 3)), 3) + #define stbir__simdf_1aaa( out, alp, ones ) (out) = vsetq_lane_f32(1.0f, vdupq_n_f32(vgetq_lane_f32(alp, 0)), 0) + + #if defined( _MSC_VER ) && !defined(__clang__) + static stbir__inline uint8x8x2_t stbir_make8x2(float32x4_t reg) + { + uint8x8x2_t r = { { vget_low_u8(vreinterpretq_u8_f32(reg)), vget_high_u8(vreinterpretq_u8_f32(reg)) } }; + return r; + } + #define stbir_make8(a,b) vcreate_u8( \ + (4*a+0) | ((4*a+1)<<8) | ((4*a+2)<<16) | ((4*a+3)<<24) | \ + ((stbir_uint64)(4*b+0)<<32) | ((stbir_uint64)(4*b+1)<<40) | ((stbir_uint64)(4*b+2)<<48) | ((stbir_uint64)(4*b+3)<<56) ) + #else + #define stbir_make8x2(reg) (uint8x8x2_t){ { vget_low_u8(vreinterpretq_u8_f32(reg)), vget_high_u8(vreinterpretq_u8_f32(reg)) } } + #define stbir_make8(a,b) (uint8x8_t){4*a+0,4*a+1,4*a+2,4*a+3,4*b+0,4*b+1,4*b+2,4*b+3} + #endif + + #define stbir__simdf_swiz( reg, one, two, three, four ) vreinterpretq_f32_u8( vcombine_u8( \ + vtbl2_u8( stbir_make8x2( reg ), stbir_make8( one, two ) ), \ + vtbl2_u8( stbir_make8x2( reg ), stbir_make8( three, four ) ) ) ) + + #define stbir__simdi_16madd( out, reg0, reg1 ) \ + { \ + int16x8_t r0 = vreinterpretq_s16_u32(reg0); \ + int16x8_t r1 = vreinterpretq_s16_u32(reg1); \ + int32x4_t tmp0 = vmull_s16( vget_low_s16(r0), vget_low_s16(r1) ); \ + int32x4_t tmp1 = vmull_s16( vget_high_s16(r0), vget_high_s16(r1) ); \ + int32x2_t out0 = vpadd_s32( vget_low_s32(tmp0), vget_high_s32(tmp0) ); \ + int32x2_t out1 = vpadd_s32( vget_low_s32(tmp1), vget_high_s32(tmp1) ); \ + (out) = vreinterpretq_u32_s32( vcombine_s32(out0, out1) ); \ + } + + #endif + + #define stbir__simdi_and( out, reg0, reg1 ) (out) = vandq_u32( reg0, reg1 ) + #define stbir__simdi_or( out, reg0, reg1 ) (out) = vorrq_u32( reg0, reg1 ) + + #define stbir__simdf_pack_to_8bytes(out,aa,bb) \ + { \ + float32x4_t af = vmaxq_f32( vminq_f32(aa,STBIR__CONSTF(STBIR_max_uint8_as_float) ), vdupq_n_f32(0) ); \ + float32x4_t bf = vmaxq_f32( vminq_f32(bb,STBIR__CONSTF(STBIR_max_uint8_as_float) ), vdupq_n_f32(0) ); \ + int16x4_t ai = vqmovn_s32( vcvtq_s32_f32( af ) ); \ + int16x4_t bi = vqmovn_s32( vcvtq_s32_f32( bf ) ); \ + uint8x8_t out8 = vqmovun_s16( vcombine_s16(ai, bi) ); \ + out = vreinterpretq_u32_u8( vcombine_u8(out8, out8) ); \ + } + + #define stbir__simdf_pack_to_8words(out,aa,bb) \ + { \ + float32x4_t af = vmaxq_f32( vminq_f32(aa,STBIR__CONSTF(STBIR_max_uint16_as_float) ), vdupq_n_f32(0) ); \ + float32x4_t bf = vmaxq_f32( vminq_f32(bb,STBIR__CONSTF(STBIR_max_uint16_as_float) ), vdupq_n_f32(0) ); \ + int32x4_t ai = vcvtq_s32_f32( af ); \ + int32x4_t bi = vcvtq_s32_f32( bf ); \ + out = vreinterpretq_u32_u16( vcombine_u16(vqmovun_s32(ai), vqmovun_s32(bi)) ); \ + } + + #define stbir__interleave_pack_and_store_16_u8( ptr, r0, r1, r2, r3 ) \ + { \ + int16x4x2_t tmp0 = vzip_s16( vqmovn_s32(vreinterpretq_s32_u32(r0)), vqmovn_s32(vreinterpretq_s32_u32(r2)) ); \ + int16x4x2_t tmp1 = vzip_s16( vqmovn_s32(vreinterpretq_s32_u32(r1)), vqmovn_s32(vreinterpretq_s32_u32(r3)) ); \ + uint8x8x2_t out = \ + { { \ + vqmovun_s16( vcombine_s16(tmp0.val[0], tmp0.val[1]) ), \ + vqmovun_s16( vcombine_s16(tmp1.val[0], tmp1.val[1]) ), \ + } }; \ + vst2_u8(ptr, out); \ + } + + #define stbir__simdf_load4_transposed( o0, o1, o2, o3, ptr ) \ + { \ + float32x4x4_t tmp = vld4q_f32(ptr); \ + o0 = tmp.val[0]; \ + o1 = tmp.val[1]; \ + o2 = tmp.val[2]; \ + o3 = tmp.val[3]; \ + } + + #define stbir__simdi_32shr( out, reg, imm ) out = vshrq_n_u32( reg, imm ) + + #if defined( _MSC_VER ) && !defined(__clang__) + #define STBIR__SIMDF_CONST(var, x) __declspec(align(8)) float var[] = { x, x, x, x } + #define STBIR__SIMDI_CONST(var, x) __declspec(align(8)) uint32_t var[] = { x, x, x, x } + #define STBIR__CONSTF(var) (*(const float32x4_t*)var) + #define STBIR__CONSTI(var) (*(const uint32x4_t*)var) + #else + #define STBIR__SIMDF_CONST(var, x) stbir__simdf var = { x, x, x, x } + #define STBIR__SIMDI_CONST(var, x) stbir__simdi var = { x, x, x, x } + #define STBIR__CONSTF(var) (var) + #define STBIR__CONSTI(var) (var) + #endif + + #ifdef STBIR_FLOORF + #undef STBIR_FLOORF + #endif + #define STBIR_FLOORF stbir_simd_floorf + static stbir__inline float stbir_simd_floorf(float x) + { + #if defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) + return vget_lane_f32( vrndm_f32( vdup_n_f32(x) ), 0); + #else + float32x2_t f = vdup_n_f32(x); + float32x2_t t = vcvt_f32_s32(vcvt_s32_f32(f)); + uint32x2_t a = vclt_f32(f, t); + uint32x2_t b = vreinterpret_u32_f32(vdup_n_f32(-1.0f)); + float32x2_t r = vadd_f32(t, vreinterpret_f32_u32(vand_u32(a, b))); + return vget_lane_f32(r, 0); + #endif + } + + #ifdef STBIR_CEILF + #undef STBIR_CEILF + #endif + #define STBIR_CEILF stbir_simd_ceilf + static stbir__inline float stbir_simd_ceilf(float x) + { + #if defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) + return vget_lane_f32( vrndp_f32( vdup_n_f32(x) ), 0); + #else + float32x2_t f = vdup_n_f32(x); + float32x2_t t = vcvt_f32_s32(vcvt_s32_f32(f)); + uint32x2_t a = vclt_f32(t, f); + uint32x2_t b = vreinterpret_u32_f32(vdup_n_f32(1.0f)); + float32x2_t r = vadd_f32(t, vreinterpret_f32_u32(vand_u32(a, b))); + return vget_lane_f32(r, 0); + #endif + } + + #define STBIR_SIMD + +#elif defined(STBIR_WASM) + + #include + + #define stbir__simdf v128_t + #define stbir__simdi v128_t + + #define stbir_simdi_castf( reg ) (reg) + #define stbir_simdf_casti( reg ) (reg) + + #define stbir__simdf_load( reg, ptr ) (reg) = wasm_v128_load( (void const*)(ptr) ) + #define stbir__simdi_load( reg, ptr ) (reg) = wasm_v128_load( (void const*)(ptr) ) + #define stbir__simdf_load1( out, ptr ) (out) = wasm_v128_load32_splat( (void const*)(ptr) ) // top values can be random (not denormal or nan for perf) + #define stbir__simdi_load1( out, ptr ) (out) = wasm_v128_load32_splat( (void const*)(ptr) ) + #define stbir__simdf_load1z( out, ptr ) (out) = wasm_v128_load32_zero( (void const*)(ptr) ) // top values must be zero + #define stbir__simdf_frep4( fvar ) wasm_f32x4_splat( fvar ) + #define stbir__simdf_load1frep4( out, fvar ) (out) = wasm_f32x4_splat( fvar ) + #define stbir__simdf_load2( out, ptr ) (out) = wasm_v128_load64_splat( (void const*)(ptr) ) // top values can be random (not denormal or nan for perf) + #define stbir__simdf_load2z( out, ptr ) (out) = wasm_v128_load64_zero( (void const*)(ptr) ) // top values must be zero + #define stbir__simdf_load2hmerge( out, reg, ptr ) (out) = wasm_v128_load64_lane( (void const*)(ptr), reg, 1 ) + + #define stbir__simdf_zeroP() wasm_f32x4_const_splat(0) + #define stbir__simdf_zero( reg ) (reg) = wasm_f32x4_const_splat(0) + + #define stbir__simdf_store( ptr, reg ) wasm_v128_store( (void*)(ptr), reg ) + #define stbir__simdf_store1( ptr, reg ) wasm_v128_store32_lane( (void*)(ptr), reg, 0 ) + #define stbir__simdf_store2( ptr, reg ) wasm_v128_store64_lane( (void*)(ptr), reg, 0 ) + #define stbir__simdf_store2h( ptr, reg ) wasm_v128_store64_lane( (void*)(ptr), reg, 1 ) + + #define stbir__simdi_store( ptr, reg ) wasm_v128_store( (void*)(ptr), reg ) + #define stbir__simdi_store1( ptr, reg ) wasm_v128_store32_lane( (void*)(ptr), reg, 0 ) + #define stbir__simdi_store2( ptr, reg ) wasm_v128_store64_lane( (void*)(ptr), reg, 0 ) + + #define stbir__prefetch( ptr ) + + #define stbir__simdi_expand_u8_to_u32(out0,out1,out2,out3,ireg) \ + { \ + v128_t l = wasm_u16x8_extend_low_u8x16 ( ireg ); \ + v128_t h = wasm_u16x8_extend_high_u8x16( ireg ); \ + out0 = wasm_u32x4_extend_low_u16x8 ( l ); \ + out1 = wasm_u32x4_extend_high_u16x8( l ); \ + out2 = wasm_u32x4_extend_low_u16x8 ( h ); \ + out3 = wasm_u32x4_extend_high_u16x8( h ); \ + } + + #define stbir__simdi_expand_u8_to_1u32(out,ireg) \ + { \ + v128_t tmp = wasm_u16x8_extend_low_u8x16(ireg); \ + out = wasm_u32x4_extend_low_u16x8(tmp); \ + } + + #define stbir__simdi_expand_u16_to_u32(out0,out1,ireg) \ + { \ + out0 = wasm_u32x4_extend_low_u16x8 ( ireg ); \ + out1 = wasm_u32x4_extend_high_u16x8( ireg ); \ + } + + #define stbir__simdf_convert_float_to_i32( i, f ) (i) = wasm_i32x4_trunc_sat_f32x4(f) + #define stbir__simdf_convert_float_to_int( f ) wasm_i32x4_extract_lane(wasm_i32x4_trunc_sat_f32x4(f), 0) + #define stbir__simdi_to_int( i ) wasm_i32x4_extract_lane(i, 0) + #define stbir__simdf_convert_float_to_uint8( f ) ((unsigned char)wasm_i32x4_extract_lane(wasm_i32x4_trunc_sat_f32x4(wasm_f32x4_max(wasm_f32x4_min(f,STBIR_max_uint8_as_float),wasm_f32x4_const_splat(0))), 0)) + #define stbir__simdf_convert_float_to_short( f ) ((unsigned short)wasm_i32x4_extract_lane(wasm_i32x4_trunc_sat_f32x4(wasm_f32x4_max(wasm_f32x4_min(f,STBIR_max_uint16_as_float),wasm_f32x4_const_splat(0))), 0)) + #define stbir__simdi_convert_i32_to_float(out, ireg) (out) = wasm_f32x4_convert_i32x4(ireg) + #define stbir__simdf_add( out, reg0, reg1 ) (out) = wasm_f32x4_add( reg0, reg1 ) + #define stbir__simdf_mult( out, reg0, reg1 ) (out) = wasm_f32x4_mul( reg0, reg1 ) + #define stbir__simdf_mult_mem( out, reg, ptr ) (out) = wasm_f32x4_mul( reg, wasm_v128_load( (void const*)(ptr) ) ) + #define stbir__simdf_mult1_mem( out, reg, ptr ) (out) = wasm_f32x4_mul( reg, wasm_v128_load32_splat( (void const*)(ptr) ) ) + #define stbir__simdf_add_mem( out, reg, ptr ) (out) = wasm_f32x4_add( reg, wasm_v128_load( (void const*)(ptr) ) ) + #define stbir__simdf_add1_mem( out, reg, ptr ) (out) = wasm_f32x4_add( reg, wasm_v128_load32_splat( (void const*)(ptr) ) ) + + #define stbir__simdf_madd( out, add, mul1, mul2 ) (out) = wasm_f32x4_add( add, wasm_f32x4_mul( mul1, mul2 ) ) + #define stbir__simdf_madd1( out, add, mul1, mul2 ) (out) = wasm_f32x4_add( add, wasm_f32x4_mul( mul1, mul2 ) ) + #define stbir__simdf_madd_mem( out, add, mul, ptr ) (out) = wasm_f32x4_add( add, wasm_f32x4_mul( mul, wasm_v128_load( (void const*)(ptr) ) ) ) + #define stbir__simdf_madd1_mem( out, add, mul, ptr ) (out) = wasm_f32x4_add( add, wasm_f32x4_mul( mul, wasm_v128_load32_splat( (void const*)(ptr) ) ) ) + + #define stbir__simdf_add1( out, reg0, reg1 ) (out) = wasm_f32x4_add( reg0, reg1 ) + #define stbir__simdf_mult1( out, reg0, reg1 ) (out) = wasm_f32x4_mul( reg0, reg1 ) + + #define stbir__simdf_and( out, reg0, reg1 ) (out) = wasm_v128_and( reg0, reg1 ) + #define stbir__simdf_or( out, reg0, reg1 ) (out) = wasm_v128_or( reg0, reg1 ) + + #define stbir__simdf_min( out, reg0, reg1 ) (out) = wasm_f32x4_min( reg0, reg1 ) + #define stbir__simdf_max( out, reg0, reg1 ) (out) = wasm_f32x4_max( reg0, reg1 ) + #define stbir__simdf_min1( out, reg0, reg1 ) (out) = wasm_f32x4_min( reg0, reg1 ) + #define stbir__simdf_max1( out, reg0, reg1 ) (out) = wasm_f32x4_max( reg0, reg1 ) + + #define stbir__simdf_0123ABCDto3ABx( out, reg0, reg1 ) (out) = wasm_i32x4_shuffle( reg0, reg1, 3, 4, 5, -1 ) + #define stbir__simdf_0123ABCDto23Ax( out, reg0, reg1 ) (out) = wasm_i32x4_shuffle( reg0, reg1, 2, 3, 4, -1 ) + + #define stbir__simdf_aaa1(out,alp,ones) (out) = wasm_i32x4_shuffle(alp, ones, 3, 3, 3, 4) + #define stbir__simdf_1aaa(out,alp,ones) (out) = wasm_i32x4_shuffle(alp, ones, 4, 0, 0, 0) + #define stbir__simdf_a1a1(out,alp,ones) (out) = wasm_i32x4_shuffle(alp, ones, 1, 4, 3, 4) + #define stbir__simdf_1a1a(out,alp,ones) (out) = wasm_i32x4_shuffle(alp, ones, 4, 0, 4, 2) + + #define stbir__simdf_swiz( reg, one, two, three, four ) wasm_i32x4_shuffle(reg, reg, one, two, three, four) + + #define stbir__simdi_and( out, reg0, reg1 ) (out) = wasm_v128_and( reg0, reg1 ) + #define stbir__simdi_or( out, reg0, reg1 ) (out) = wasm_v128_or( reg0, reg1 ) + #define stbir__simdi_16madd( out, reg0, reg1 ) (out) = wasm_i32x4_dot_i16x8( reg0, reg1 ) + + #define stbir__simdf_pack_to_8bytes(out,aa,bb) \ + { \ + v128_t af = wasm_f32x4_max( wasm_f32x4_min(aa, STBIR_max_uint8_as_float), wasm_f32x4_const_splat(0) ); \ + v128_t bf = wasm_f32x4_max( wasm_f32x4_min(bb, STBIR_max_uint8_as_float), wasm_f32x4_const_splat(0) ); \ + v128_t ai = wasm_i32x4_trunc_sat_f32x4( af ); \ + v128_t bi = wasm_i32x4_trunc_sat_f32x4( bf ); \ + v128_t out16 = wasm_i16x8_narrow_i32x4( ai, bi ); \ + out = wasm_u8x16_narrow_i16x8( out16, out16 ); \ + } + + #define stbir__simdf_pack_to_8words(out,aa,bb) \ + { \ + v128_t af = wasm_f32x4_max( wasm_f32x4_min(aa, STBIR_max_uint16_as_float), wasm_f32x4_const_splat(0)); \ + v128_t bf = wasm_f32x4_max( wasm_f32x4_min(bb, STBIR_max_uint16_as_float), wasm_f32x4_const_splat(0)); \ + v128_t ai = wasm_i32x4_trunc_sat_f32x4( af ); \ + v128_t bi = wasm_i32x4_trunc_sat_f32x4( bf ); \ + out = wasm_u16x8_narrow_i32x4( ai, bi ); \ + } + + #define stbir__interleave_pack_and_store_16_u8( ptr, r0, r1, r2, r3 ) \ + { \ + v128_t tmp0 = wasm_i16x8_narrow_i32x4(r0, r1); \ + v128_t tmp1 = wasm_i16x8_narrow_i32x4(r2, r3); \ + v128_t tmp = wasm_u8x16_narrow_i16x8(tmp0, tmp1); \ + tmp = wasm_i8x16_shuffle(tmp, tmp, 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15); \ + wasm_v128_store( (void*)(ptr), tmp); \ + } + + #define stbir__simdf_load4_transposed( o0, o1, o2, o3, ptr ) \ + { \ + v128_t t0 = wasm_v128_load( ptr ); \ + v128_t t1 = wasm_v128_load( ptr+4 ); \ + v128_t t2 = wasm_v128_load( ptr+8 ); \ + v128_t t3 = wasm_v128_load( ptr+12 ); \ + v128_t s0 = wasm_i32x4_shuffle(t0, t1, 0, 4, 2, 6); \ + v128_t s1 = wasm_i32x4_shuffle(t0, t1, 1, 5, 3, 7); \ + v128_t s2 = wasm_i32x4_shuffle(t2, t3, 0, 4, 2, 6); \ + v128_t s3 = wasm_i32x4_shuffle(t2, t3, 1, 5, 3, 7); \ + o0 = wasm_i32x4_shuffle(s0, s2, 0, 1, 4, 5); \ + o1 = wasm_i32x4_shuffle(s1, s3, 0, 1, 4, 5); \ + o2 = wasm_i32x4_shuffle(s0, s2, 2, 3, 6, 7); \ + o3 = wasm_i32x4_shuffle(s1, s3, 2, 3, 6, 7); \ + } + + #define stbir__simdi_32shr( out, reg, imm ) out = wasm_u32x4_shr( reg, imm ) + + typedef float stbir__f32x4 __attribute__((__vector_size__(16), __aligned__(16))); + #define STBIR__SIMDF_CONST(var, x) stbir__simdf var = (v128_t)(stbir__f32x4){ x, x, x, x } + #define STBIR__SIMDI_CONST(var, x) stbir__simdi var = { x, x, x, x } + #define STBIR__CONSTF(var) (var) + #define STBIR__CONSTI(var) (var) + + #ifdef STBIR_FLOORF + #undef STBIR_FLOORF + #endif + #define STBIR_FLOORF stbir_simd_floorf + static stbir__inline float stbir_simd_floorf(float x) + { + return wasm_f32x4_extract_lane( wasm_f32x4_floor( wasm_f32x4_splat(x) ), 0); + } + + #ifdef STBIR_CEILF + #undef STBIR_CEILF + #endif + #define STBIR_CEILF stbir_simd_ceilf + static stbir__inline float stbir_simd_ceilf(float x) + { + return wasm_f32x4_extract_lane( wasm_f32x4_ceil( wasm_f32x4_splat(x) ), 0); + } + + #define STBIR_SIMD + +#endif // SSE2/NEON/WASM + +#endif // NO SIMD + +#ifdef STBIR_SIMD8 + #define stbir__simdfX stbir__simdf8 + #define stbir__simdiX stbir__simdi8 + #define stbir__simdfX_load stbir__simdf8_load + #define stbir__simdiX_load stbir__simdi8_load + #define stbir__simdfX_mult stbir__simdf8_mult + #define stbir__simdfX_add_mem stbir__simdf8_add_mem + #define stbir__simdfX_madd_mem stbir__simdf8_madd_mem + #define stbir__simdfX_store stbir__simdf8_store + #define stbir__simdiX_store stbir__simdi8_store + #define stbir__simdf_frepX stbir__simdf8_frep8 + #define stbir__simdfX_madd stbir__simdf8_madd + #define stbir__simdfX_min stbir__simdf8_min + #define stbir__simdfX_max stbir__simdf8_max + #define stbir__simdfX_aaa1 stbir__simdf8_aaa1 + #define stbir__simdfX_1aaa stbir__simdf8_1aaa + #define stbir__simdfX_a1a1 stbir__simdf8_a1a1 + #define stbir__simdfX_1a1a stbir__simdf8_1a1a + #define stbir__simdfX_convert_float_to_i32 stbir__simdf8_convert_float_to_i32 + #define stbir__simdfX_pack_to_words stbir__simdf8_pack_to_16words + #define stbir__simdfX_zero stbir__simdf8_zero + #define STBIR_onesX STBIR_ones8 + #define STBIR_max_uint8_as_floatX STBIR_max_uint8_as_float8 + #define STBIR_max_uint16_as_floatX STBIR_max_uint16_as_float8 + #define STBIR_simd_point5X STBIR_simd_point58 + #define stbir__simdfX_float_count 8 + #define stbir__simdfX_0123to1230 stbir__simdf8_0123to12301230 + #define stbir__simdfX_0123to2103 stbir__simdf8_0123to21032103 + static const stbir__simdf8 STBIR_max_uint16_as_float_inverted8 = { stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted }; + static const stbir__simdf8 STBIR_max_uint8_as_float_inverted8 = { stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted }; + static const stbir__simdf8 STBIR_ones8 = { 1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0 }; + static const stbir__simdf8 STBIR_simd_point58 = { 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5 }; + static const stbir__simdf8 STBIR_max_uint8_as_float8 = { stbir__max_uint8_as_float,stbir__max_uint8_as_float,stbir__max_uint8_as_float,stbir__max_uint8_as_float, stbir__max_uint8_as_float,stbir__max_uint8_as_float,stbir__max_uint8_as_float,stbir__max_uint8_as_float }; + static const stbir__simdf8 STBIR_max_uint16_as_float8 = { stbir__max_uint16_as_float,stbir__max_uint16_as_float,stbir__max_uint16_as_float,stbir__max_uint16_as_float, stbir__max_uint16_as_float,stbir__max_uint16_as_float,stbir__max_uint16_as_float,stbir__max_uint16_as_float }; +#else + #define stbir__simdfX stbir__simdf + #define stbir__simdiX stbir__simdi + #define stbir__simdfX_load stbir__simdf_load + #define stbir__simdiX_load stbir__simdi_load + #define stbir__simdfX_mult stbir__simdf_mult + #define stbir__simdfX_add_mem stbir__simdf_add_mem + #define stbir__simdfX_madd_mem stbir__simdf_madd_mem + #define stbir__simdfX_store stbir__simdf_store + #define stbir__simdiX_store stbir__simdi_store + #define stbir__simdf_frepX stbir__simdf_frep4 + #define stbir__simdfX_madd stbir__simdf_madd + #define stbir__simdfX_min stbir__simdf_min + #define stbir__simdfX_max stbir__simdf_max + #define stbir__simdfX_aaa1 stbir__simdf_aaa1 + #define stbir__simdfX_1aaa stbir__simdf_1aaa + #define stbir__simdfX_a1a1 stbir__simdf_a1a1 + #define stbir__simdfX_1a1a stbir__simdf_1a1a + #define stbir__simdfX_convert_float_to_i32 stbir__simdf_convert_float_to_i32 + #define stbir__simdfX_pack_to_words stbir__simdf_pack_to_8words + #define stbir__simdfX_zero stbir__simdf_zero + #define STBIR_onesX STBIR__CONSTF(STBIR_ones) + #define STBIR_simd_point5X STBIR__CONSTF(STBIR_simd_point5) + #define STBIR_max_uint8_as_floatX STBIR__CONSTF(STBIR_max_uint8_as_float) + #define STBIR_max_uint16_as_floatX STBIR__CONSTF(STBIR_max_uint16_as_float) + #define stbir__simdfX_float_count 4 + #define stbir__if_simdf8_cast_to_simdf4( val ) ( val ) + #define stbir__simdfX_0123to1230 stbir__simdf_0123to1230 + #define stbir__simdfX_0123to2103 stbir__simdf_0123to2103 +#endif + + +#if defined(STBIR_NEON) && !defined(_M_ARM) + + #if defined( _MSC_VER ) && !defined(__clang__) + typedef __int16 stbir__FP16; + #else + typedef float16_t stbir__FP16; + #endif + +#else // no NEON, or 32-bit ARM for MSVC + + typedef union stbir__FP16 + { + unsigned short u; + } stbir__FP16; + +#endif + +#if !defined(STBIR_NEON) && !defined(STBIR_FP16C) || defined(STBIR_NEON) && defined(_M_ARM) + + // Fabian's half float routines, see: https://gist.github.com/rygorous/2156668 + + static stbir__inline float stbir__half_to_float( stbir__FP16 h ) + { + static const stbir__FP32 magic = { (254 - 15) << 23 }; + static const stbir__FP32 was_infnan = { (127 + 16) << 23 }; + stbir__FP32 o; + + o.u = (h.u & 0x7fff) << 13; // exponent/mantissa bits + o.f *= magic.f; // exponent adjust + if (o.f >= was_infnan.f) // make sure Inf/NaN survive + o.u |= 255 << 23; + o.u |= (h.u & 0x8000) << 16; // sign bit + return o.f; + } + + static stbir__inline stbir__FP16 stbir__float_to_half(float val) + { + stbir__FP32 f32infty = { 255 << 23 }; + stbir__FP32 f16max = { (127 + 16) << 23 }; + stbir__FP32 denorm_magic = { ((127 - 15) + (23 - 10) + 1) << 23 }; + unsigned int sign_mask = 0x80000000u; + stbir__FP16 o = { 0 }; + stbir__FP32 f; + unsigned int sign; + + f.f = val; + sign = f.u & sign_mask; + f.u ^= sign; + + if (f.u >= f16max.u) // result is Inf or NaN (all exponent bits set) + o.u = (f.u > f32infty.u) ? 0x7e00 : 0x7c00; // NaN->qNaN and Inf->Inf + else // (De)normalized number or zero + { + if (f.u < (113 << 23)) // resulting FP16 is subnormal or zero + { + // use a magic value to align our 10 mantissa bits at the bottom of + // the float. as long as FP addition is round-to-nearest-even this + // just works. + f.f += denorm_magic.f; + // and one integer subtract of the bias later, we have our final float! + o.u = (unsigned short) ( f.u - denorm_magic.u ); + } + else + { + unsigned int mant_odd = (f.u >> 13) & 1; // resulting mantissa is odd + // update exponent, rounding bias part 1 + f.u = f.u + ((15u - 127) << 23) + 0xfff; + // rounding bias part 2 + f.u += mant_odd; + // take the bits! + o.u = (unsigned short) ( f.u >> 13 ); + } + } + + o.u |= sign >> 16; + return o; + } + +#endif + + +#if defined(STBIR_FP16C) + + #include + + static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) + { + _mm256_storeu_ps( (float*)output, _mm256_cvtph_ps( _mm_loadu_si128( (__m128i const* )input ) ) ); + } + + static stbir__inline void stbir__float_to_half_SIMD(stbir__FP16 * output, float const * input) + { + _mm_storeu_si128( (__m128i*)output, _mm256_cvtps_ph( _mm256_loadu_ps( input ), 0 ) ); + } + + static stbir__inline float stbir__half_to_float( stbir__FP16 h ) + { + return _mm_cvtss_f32( _mm_cvtph_ps( _mm_cvtsi32_si128( (int)h.u ) ) ); + } + + static stbir__inline stbir__FP16 stbir__float_to_half( float f ) + { + stbir__FP16 h; + h.u = (unsigned short) _mm_cvtsi128_si32( _mm_cvtps_ph( _mm_set_ss( f ), 0 ) ); + return h; + } + +#elif defined(STBIR_SSE2) + + // Fabian's half float routines, see: https://gist.github.com/rygorous/2156668 + stbir__inline static void stbir__half_to_float_SIMD(float * output, void const * input) + { + static const STBIR__SIMDI_CONST(mask_nosign, 0x7fff); + static const STBIR__SIMDI_CONST(smallest_normal, 0x0400); + static const STBIR__SIMDI_CONST(infinity, 0x7c00); + static const STBIR__SIMDI_CONST(expadjust_normal, (127 - 15) << 23); + static const STBIR__SIMDI_CONST(magic_denorm, 113 << 23); + + __m128i i = _mm_loadu_si128 ( (__m128i const*)(input) ); + __m128i h = _mm_unpacklo_epi16 ( i, _mm_setzero_si128() ); + __m128i mnosign = STBIR__CONSTI(mask_nosign); + __m128i eadjust = STBIR__CONSTI(expadjust_normal); + __m128i smallest = STBIR__CONSTI(smallest_normal); + __m128i infty = STBIR__CONSTI(infinity); + __m128i expmant = _mm_and_si128(mnosign, h); + __m128i justsign = _mm_xor_si128(h, expmant); + __m128i b_notinfnan = _mm_cmpgt_epi32(infty, expmant); + __m128i b_isdenorm = _mm_cmpgt_epi32(smallest, expmant); + __m128i shifted = _mm_slli_epi32(expmant, 13); + __m128i adj_infnan = _mm_andnot_si128(b_notinfnan, eadjust); + __m128i adjusted = _mm_add_epi32(eadjust, shifted); + __m128i den1 = _mm_add_epi32(shifted, STBIR__CONSTI(magic_denorm)); + __m128i adjusted2 = _mm_add_epi32(adjusted, adj_infnan); + __m128 den2 = _mm_sub_ps(_mm_castsi128_ps(den1), *(const __m128 *)&magic_denorm); + __m128 adjusted3 = _mm_and_ps(den2, _mm_castsi128_ps(b_isdenorm)); + __m128 adjusted4 = _mm_andnot_ps(_mm_castsi128_ps(b_isdenorm), _mm_castsi128_ps(adjusted2)); + __m128 adjusted5 = _mm_or_ps(adjusted3, adjusted4); + __m128i sign = _mm_slli_epi32(justsign, 16); + __m128 final = _mm_or_ps(adjusted5, _mm_castsi128_ps(sign)); + stbir__simdf_store( output + 0, final ); + + h = _mm_unpackhi_epi16 ( i, _mm_setzero_si128() ); + expmant = _mm_and_si128(mnosign, h); + justsign = _mm_xor_si128(h, expmant); + b_notinfnan = _mm_cmpgt_epi32(infty, expmant); + b_isdenorm = _mm_cmpgt_epi32(smallest, expmant); + shifted = _mm_slli_epi32(expmant, 13); + adj_infnan = _mm_andnot_si128(b_notinfnan, eadjust); + adjusted = _mm_add_epi32(eadjust, shifted); + den1 = _mm_add_epi32(shifted, STBIR__CONSTI(magic_denorm)); + adjusted2 = _mm_add_epi32(adjusted, adj_infnan); + den2 = _mm_sub_ps(_mm_castsi128_ps(den1), *(const __m128 *)&magic_denorm); + adjusted3 = _mm_and_ps(den2, _mm_castsi128_ps(b_isdenorm)); + adjusted4 = _mm_andnot_ps(_mm_castsi128_ps(b_isdenorm), _mm_castsi128_ps(adjusted2)); + adjusted5 = _mm_or_ps(adjusted3, adjusted4); + sign = _mm_slli_epi32(justsign, 16); + final = _mm_or_ps(adjusted5, _mm_castsi128_ps(sign)); + stbir__simdf_store( output + 4, final ); + + // ~38 SSE2 ops for 8 values + } + + // Fabian's round-to-nearest-even float to half + // ~48 SSE2 ops for 8 output + stbir__inline static void stbir__float_to_half_SIMD(void * output, float const * input) + { + static const STBIR__SIMDI_CONST(mask_sign, 0x80000000u); + static const STBIR__SIMDI_CONST(c_f16max, (127 + 16) << 23); // all FP32 values >=this round to +inf + static const STBIR__SIMDI_CONST(c_nanbit, 0x200); + static const STBIR__SIMDI_CONST(c_infty_as_fp16, 0x7c00); + static const STBIR__SIMDI_CONST(c_min_normal, (127 - 14) << 23); // smallest FP32 that yields a normalized FP16 + static const STBIR__SIMDI_CONST(c_subnorm_magic, ((127 - 15) + (23 - 10) + 1) << 23); + static const STBIR__SIMDI_CONST(c_normal_bias, 0xfff - ((127 - 15) << 23)); // adjust exponent and add mantissa rounding + + __m128 f = _mm_loadu_ps(input); + __m128 msign = _mm_castsi128_ps(STBIR__CONSTI(mask_sign)); + __m128 justsign = _mm_and_ps(msign, f); + __m128 absf = _mm_xor_ps(f, justsign); + __m128i absf_int = _mm_castps_si128(absf); // the cast is "free" (extra bypass latency, but no thruput hit) + __m128i f16max = STBIR__CONSTI(c_f16max); + __m128 b_isnan = _mm_cmpunord_ps(absf, absf); // is this a NaN? + __m128i b_isregular = _mm_cmpgt_epi32(f16max, absf_int); // (sub)normalized or special? + __m128i nanbit = _mm_and_si128(_mm_castps_si128(b_isnan), STBIR__CONSTI(c_nanbit)); + __m128i inf_or_nan = _mm_or_si128(nanbit, STBIR__CONSTI(c_infty_as_fp16)); // output for specials + + __m128i min_normal = STBIR__CONSTI(c_min_normal); + __m128i b_issub = _mm_cmpgt_epi32(min_normal, absf_int); + + // "result is subnormal" path + __m128 subnorm1 = _mm_add_ps(absf, _mm_castsi128_ps(STBIR__CONSTI(c_subnorm_magic))); // magic value to round output mantissa + __m128i subnorm2 = _mm_sub_epi32(_mm_castps_si128(subnorm1), STBIR__CONSTI(c_subnorm_magic)); // subtract out bias + + // "result is normal" path + __m128i mantoddbit = _mm_slli_epi32(absf_int, 31 - 13); // shift bit 13 (mantissa LSB) to sign + __m128i mantodd = _mm_srai_epi32(mantoddbit, 31); // -1 if FP16 mantissa odd, else 0 + + __m128i round1 = _mm_add_epi32(absf_int, STBIR__CONSTI(c_normal_bias)); + __m128i round2 = _mm_sub_epi32(round1, mantodd); // if mantissa LSB odd, bias towards rounding up (RTNE) + __m128i normal = _mm_srli_epi32(round2, 13); // rounded result + + // combine the two non-specials + __m128i nonspecial = _mm_or_si128(_mm_and_si128(subnorm2, b_issub), _mm_andnot_si128(b_issub, normal)); + + // merge in specials as well + __m128i joined = _mm_or_si128(_mm_and_si128(nonspecial, b_isregular), _mm_andnot_si128(b_isregular, inf_or_nan)); + + __m128i sign_shift = _mm_srai_epi32(_mm_castps_si128(justsign), 16); + __m128i final2, final= _mm_or_si128(joined, sign_shift); + + f = _mm_loadu_ps(input+4); + justsign = _mm_and_ps(msign, f); + absf = _mm_xor_ps(f, justsign); + absf_int = _mm_castps_si128(absf); // the cast is "free" (extra bypass latency, but no thruput hit) + b_isnan = _mm_cmpunord_ps(absf, absf); // is this a NaN? + b_isregular = _mm_cmpgt_epi32(f16max, absf_int); // (sub)normalized or special? + nanbit = _mm_and_si128(_mm_castps_si128(b_isnan), c_nanbit); + inf_or_nan = _mm_or_si128(nanbit, STBIR__CONSTI(c_infty_as_fp16)); // output for specials + + b_issub = _mm_cmpgt_epi32(min_normal, absf_int); + + // "result is subnormal" path + subnorm1 = _mm_add_ps(absf, _mm_castsi128_ps(STBIR__CONSTI(c_subnorm_magic))); // magic value to round output mantissa + subnorm2 = _mm_sub_epi32(_mm_castps_si128(subnorm1), STBIR__CONSTI(c_subnorm_magic)); // subtract out bias + + // "result is normal" path + mantoddbit = _mm_slli_epi32(absf_int, 31 - 13); // shift bit 13 (mantissa LSB) to sign + mantodd = _mm_srai_epi32(mantoddbit, 31); // -1 if FP16 mantissa odd, else 0 + + round1 = _mm_add_epi32(absf_int, STBIR__CONSTI(c_normal_bias)); + round2 = _mm_sub_epi32(round1, mantodd); // if mantissa LSB odd, bias towards rounding up (RTNE) + normal = _mm_srli_epi32(round2, 13); // rounded result + + // combine the two non-specials + nonspecial = _mm_or_si128(_mm_and_si128(subnorm2, b_issub), _mm_andnot_si128(b_issub, normal)); + + // merge in specials as well + joined = _mm_or_si128(_mm_and_si128(nonspecial, b_isregular), _mm_andnot_si128(b_isregular, inf_or_nan)); + + sign_shift = _mm_srai_epi32(_mm_castps_si128(justsign), 16); + final2 = _mm_or_si128(joined, sign_shift); + final = _mm_packs_epi32(final, final2); + stbir__simdi_store( output,final ); + } + +#elif defined(STBIR_WASM) || (defined(STBIR_NEON) && defined(_MSC_VER) && defined(_M_ARM)) // WASM or 32-bit ARM on MSVC/clang + + static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) + { + for (int i=0; i<8; i++) + { + output[i] = stbir__half_to_float(input[i]); + } + } + + static stbir__inline void stbir__float_to_half_SIMD(stbir__FP16 * output, float const * input) + { + for (int i=0; i<8; i++) + { + output[i] = stbir__float_to_half(input[i]); + } + } + +#elif defined(STBIR_NEON) && defined(_MSC_VER) && defined(_M_ARM64) && !defined(__clang__) // 64-bit ARM on MSVC (not clang) + + static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) + { + float16x4_t in0 = vld1_f16(input + 0); + float16x4_t in1 = vld1_f16(input + 4); + vst1q_f32(output + 0, vcvt_f32_f16(in0)); + vst1q_f32(output + 4, vcvt_f32_f16(in1)); + } + + static stbir__inline void stbir__float_to_half_SIMD(stbir__FP16 * output, float const * input) + { + float16x4_t out0 = vcvt_f16_f32(vld1q_f32(input + 0)); + float16x4_t out1 = vcvt_f16_f32(vld1q_f32(input + 4)); + vst1_f16(output+0, out0); + vst1_f16(output+4, out1); + } + + static stbir__inline float stbir__half_to_float( stbir__FP16 h ) + { + return vgetq_lane_f32(vcvt_f32_f16(vld1_dup_f16(&h)), 0); + } + + static stbir__inline stbir__FP16 stbir__float_to_half( float f ) + { + return vget_lane_f16(vcvt_f16_f32(vdupq_n_f32(f)), 0).n16_u16[0]; + } + +#elif defined(STBIR_NEON) // 64-bit ARM + + static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) + { + float16x8_t in = vld1q_f16(input); + vst1q_f32(output + 0, vcvt_f32_f16(vget_low_f16(in))); + vst1q_f32(output + 4, vcvt_f32_f16(vget_high_f16(in))); + } + + static stbir__inline void stbir__float_to_half_SIMD(stbir__FP16 * output, float const * input) + { + float16x4_t out0 = vcvt_f16_f32(vld1q_f32(input + 0)); + float16x4_t out1 = vcvt_f16_f32(vld1q_f32(input + 4)); + vst1q_f16(output, vcombine_f16(out0, out1)); + } + + static stbir__inline float stbir__half_to_float( stbir__FP16 h ) + { + return vgetq_lane_f32(vcvt_f32_f16(vdup_n_f16(h)), 0); + } + + static stbir__inline stbir__FP16 stbir__float_to_half( float f ) + { + return vget_lane_f16(vcvt_f16_f32(vdupq_n_f32(f)), 0); + } + +#endif + + +#ifdef STBIR_SIMD + +#define stbir__simdf_0123to3333( out, reg ) (out) = stbir__simdf_swiz( reg, 3,3,3,3 ) +#define stbir__simdf_0123to2222( out, reg ) (out) = stbir__simdf_swiz( reg, 2,2,2,2 ) +#define stbir__simdf_0123to1111( out, reg ) (out) = stbir__simdf_swiz( reg, 1,1,1,1 ) +#define stbir__simdf_0123to0000( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,0,0 ) +#define stbir__simdf_0123to0003( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,0,3 ) +#define stbir__simdf_0123to0001( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,0,1 ) +#define stbir__simdf_0123to1122( out, reg ) (out) = stbir__simdf_swiz( reg, 1,1,2,2 ) +#define stbir__simdf_0123to2333( out, reg ) (out) = stbir__simdf_swiz( reg, 2,3,3,3 ) +#define stbir__simdf_0123to0023( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,2,3 ) +#define stbir__simdf_0123to1230( out, reg ) (out) = stbir__simdf_swiz( reg, 1,2,3,0 ) +#define stbir__simdf_0123to2103( out, reg ) (out) = stbir__simdf_swiz( reg, 2,1,0,3 ) +#define stbir__simdf_0123to3210( out, reg ) (out) = stbir__simdf_swiz( reg, 3,2,1,0 ) +#define stbir__simdf_0123to2301( out, reg ) (out) = stbir__simdf_swiz( reg, 2,3,0,1 ) +#define stbir__simdf_0123to3012( out, reg ) (out) = stbir__simdf_swiz( reg, 3,0,1,2 ) +#define stbir__simdf_0123to0011( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,1,1 ) +#define stbir__simdf_0123to1100( out, reg ) (out) = stbir__simdf_swiz( reg, 1,1,0,0 ) +#define stbir__simdf_0123to2233( out, reg ) (out) = stbir__simdf_swiz( reg, 2,2,3,3 ) +#define stbir__simdf_0123to1133( out, reg ) (out) = stbir__simdf_swiz( reg, 1,1,3,3 ) +#define stbir__simdf_0123to0022( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,2,2 ) +#define stbir__simdf_0123to1032( out, reg ) (out) = stbir__simdf_swiz( reg, 1,0,3,2 ) + +typedef union stbir__simdi_u32 +{ + stbir_uint32 m128i_u32[4]; + int m128i_i32[4]; + stbir__simdi m128i_i128; +} stbir__simdi_u32; + +static const int STBIR_mask[9] = { 0,0,0,-1,-1,-1,0,0,0 }; + +static const STBIR__SIMDF_CONST(STBIR_max_uint8_as_float, stbir__max_uint8_as_float); +static const STBIR__SIMDF_CONST(STBIR_max_uint16_as_float, stbir__max_uint16_as_float); +static const STBIR__SIMDF_CONST(STBIR_max_uint8_as_float_inverted, stbir__max_uint8_as_float_inverted); +static const STBIR__SIMDF_CONST(STBIR_max_uint16_as_float_inverted, stbir__max_uint16_as_float_inverted); + +static const STBIR__SIMDF_CONST(STBIR_simd_point5, 0.5f); +static const STBIR__SIMDF_CONST(STBIR_ones, 1.0f); +static const STBIR__SIMDI_CONST(STBIR_almost_zero, (127 - 13) << 23); +static const STBIR__SIMDI_CONST(STBIR_almost_one, 0x3f7fffff); +static const STBIR__SIMDI_CONST(STBIR_mastissa_mask, 0xff); +static const STBIR__SIMDI_CONST(STBIR_topscale, 0x02000000); + +// Basically, in simd mode, we unroll the proper amount, and we don't want +// the non-simd remnant loops to be unroll because they only run a few times +// Adding this switch saves about 5K on clang which is Captain Unroll the 3rd. +#define STBIR_SIMD_STREAMOUT_PTR( star ) STBIR_STREAMOUT_PTR( star ) +#define STBIR_SIMD_NO_UNROLL(ptr) STBIR_NO_UNROLL(ptr) + +#ifdef STBIR_MEMCPY +#undef STBIR_MEMCPY +#define STBIR_MEMCPY stbir_simd_memcpy +#endif + +// override normal use of memcpy with much simpler copy (faster and smaller with our sized copies) +static void stbir_simd_memcpy( void * dest, void const * src, size_t bytes ) +{ + char STBIR_SIMD_STREAMOUT_PTR (*) d = (char*) dest; + char STBIR_SIMD_STREAMOUT_PTR( * ) d_end = ((char*) dest) + bytes; + ptrdiff_t ofs_to_src = (char*)src - (char*)dest; + + // check overlaps + STBIR_ASSERT( ( ( d >= ( (char*)src) + bytes ) ) || ( ( d + bytes ) <= (char*)src ) ); + + if ( bytes < (16*stbir__simdfX_float_count) ) + { + if ( bytes < 16 ) + { + if ( bytes ) + { + do + { + STBIR_SIMD_NO_UNROLL(d); + d[ 0 ] = d[ ofs_to_src ]; + ++d; + } while ( d < d_end ); + } + } + else + { + stbir__simdf x; + // do one unaligned to get us aligned for the stream out below + stbir__simdf_load( x, ( d + ofs_to_src ) ); + stbir__simdf_store( d, x ); + d = (char*)( ( ( (ptrdiff_t)d ) + 16 ) & ~15 ); + + for(;;) + { + STBIR_SIMD_NO_UNROLL(d); + + if ( d > ( d_end - 16 ) ) + { + if ( d == d_end ) + return; + d = d_end - 16; + } + + stbir__simdf_load( x, ( d + ofs_to_src ) ); + stbir__simdf_store( d, x ); + d += 16; + } + } + } + else + { + stbir__simdfX x0,x1,x2,x3; + + // do one unaligned to get us aligned for the stream out below + stbir__simdfX_load( x0, ( d + ofs_to_src ) + 0*stbir__simdfX_float_count ); + stbir__simdfX_load( x1, ( d + ofs_to_src ) + 4*stbir__simdfX_float_count ); + stbir__simdfX_load( x2, ( d + ofs_to_src ) + 8*stbir__simdfX_float_count ); + stbir__simdfX_load( x3, ( d + ofs_to_src ) + 12*stbir__simdfX_float_count ); + stbir__simdfX_store( d + 0*stbir__simdfX_float_count, x0 ); + stbir__simdfX_store( d + 4*stbir__simdfX_float_count, x1 ); + stbir__simdfX_store( d + 8*stbir__simdfX_float_count, x2 ); + stbir__simdfX_store( d + 12*stbir__simdfX_float_count, x3 ); + d = (char*)( ( ( (ptrdiff_t)d ) + (16*stbir__simdfX_float_count) ) & ~((16*stbir__simdfX_float_count)-1) ); + + for(;;) + { + STBIR_SIMD_NO_UNROLL(d); + + if ( d > ( d_end - (16*stbir__simdfX_float_count) ) ) + { + if ( d == d_end ) + return; + d = d_end - (16*stbir__simdfX_float_count); + } + + stbir__simdfX_load( x0, ( d + ofs_to_src ) + 0*stbir__simdfX_float_count ); + stbir__simdfX_load( x1, ( d + ofs_to_src ) + 4*stbir__simdfX_float_count ); + stbir__simdfX_load( x2, ( d + ofs_to_src ) + 8*stbir__simdfX_float_count ); + stbir__simdfX_load( x3, ( d + ofs_to_src ) + 12*stbir__simdfX_float_count ); + stbir__simdfX_store( d + 0*stbir__simdfX_float_count, x0 ); + stbir__simdfX_store( d + 4*stbir__simdfX_float_count, x1 ); + stbir__simdfX_store( d + 8*stbir__simdfX_float_count, x2 ); + stbir__simdfX_store( d + 12*stbir__simdfX_float_count, x3 ); + d += (16*stbir__simdfX_float_count); + } + } +} + +// memcpy that is specically intentionally overlapping (src is smaller then dest, so can be +// a normal forward copy, bytes is divisible by 4 and bytes is greater than or equal to +// the diff between dest and src) +static void stbir_overlapping_memcpy( void * dest, void const * src, size_t bytes ) +{ + char STBIR_SIMD_STREAMOUT_PTR (*) sd = (char*) src; + char STBIR_SIMD_STREAMOUT_PTR( * ) s_end = ((char*) src) + bytes; + ptrdiff_t ofs_to_dest = (char*)dest - (char*)src; + + if ( ofs_to_dest >= 16 ) // is the overlap more than 16 away? + { + char STBIR_SIMD_STREAMOUT_PTR( * ) s_end16 = ((char*) src) + (bytes&~15); + do + { + stbir__simdf x; + STBIR_SIMD_NO_UNROLL(sd); + stbir__simdf_load( x, sd ); + stbir__simdf_store( ( sd + ofs_to_dest ), x ); + sd += 16; + } while ( sd < s_end16 ); + + if ( sd == s_end ) + return; + } + + do + { + STBIR_SIMD_NO_UNROLL(sd); + *(int*)( sd + ofs_to_dest ) = *(int*) sd; + sd += 4; + } while ( sd < s_end ); +} + +#else // no SSE2 + +// when in scalar mode, we let unrolling happen, so this macro just does the __restrict +#define STBIR_SIMD_STREAMOUT_PTR( star ) STBIR_STREAMOUT_PTR( star ) +#define STBIR_SIMD_NO_UNROLL(ptr) + +#endif // SSE2 + + +#ifdef STBIR_PROFILE + +#if defined(_x86_64) || defined( __x86_64__ ) || defined( _M_X64 ) || defined(__x86_64) || defined(__SSE2__) || defined(STBIR_SSE) || defined( _M_IX86_FP ) || defined(__i386) || defined( __i386__ ) || defined( _M_IX86 ) || defined( _X86_ ) + +#ifdef _MSC_VER + + STBIRDEF stbir_uint64 __rdtsc(); + #define STBIR_PROFILE_FUNC() __rdtsc() + +#else // non msvc + + static stbir__inline stbir_uint64 STBIR_PROFILE_FUNC() + { + stbir_uint32 lo, hi; + asm volatile ("rdtsc" : "=a" (lo), "=d" (hi) ); + return ( ( (stbir_uint64) hi ) << 32 ) | ( (stbir_uint64) lo ); + } + +#endif // msvc + +#elif defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) || defined(__ARM_NEON__) + +#if defined( _MSC_VER ) && !defined(__clang__) + + #define STBIR_PROFILE_FUNC() _ReadStatusReg(ARM64_CNTVCT) + +#else + + static stbir__inline stbir_uint64 STBIR_PROFILE_FUNC() + { + stbir_uint64 tsc; + asm volatile("mrs %0, cntvct_el0" : "=r" (tsc)); + return tsc; + } + +#endif + +#else // x64, arm + +#error Unknown platform for profiling. + +#endif //x64 and + + +#define STBIR_ONLY_PROFILE_GET_SPLIT_INFO ,stbir__per_split_info * split_info +#define STBIR_ONLY_PROFILE_SET_SPLIT_INFO ,split_info + +#define STBIR_ONLY_PROFILE_BUILD_GET_INFO ,stbir__info * profile_info +#define STBIR_ONLY_PROFILE_BUILD_SET_INFO ,profile_info + +// super light-weight micro profiler +#define STBIR_PROFILE_START_ll( info, wh ) { stbir_uint64 wh##thiszonetime = STBIR_PROFILE_FUNC(); stbir_uint64 * wh##save_parent_excluded_ptr = info->current_zone_excluded_ptr; stbir_uint64 wh##current_zone_excluded = 0; info->current_zone_excluded_ptr = &wh##current_zone_excluded; +#define STBIR_PROFILE_END_ll( info, wh ) wh##thiszonetime = STBIR_PROFILE_FUNC() - wh##thiszonetime; info->profile.named.wh += wh##thiszonetime - wh##current_zone_excluded; *wh##save_parent_excluded_ptr += wh##thiszonetime; info->current_zone_excluded_ptr = wh##save_parent_excluded_ptr; } +#define STBIR_PROFILE_FIRST_START_ll( info, wh ) { int i; info->current_zone_excluded_ptr = &info->profile.named.total; for(i=0;iprofile.array);i++) info->profile.array[i]=0; } STBIR_PROFILE_START_ll( info, wh ); +#define STBIR_PROFILE_CLEAR_EXTRAS_ll( info, num ) { int extra; for(extra=1;extra<(num);extra++) { int i; for(i=0;iprofile.array);i++) (info)[extra].profile.array[i]=0; } } + +// for thread data +#define STBIR_PROFILE_START( wh ) STBIR_PROFILE_START_ll( split_info, wh ) +#define STBIR_PROFILE_END( wh ) STBIR_PROFILE_END_ll( split_info, wh ) +#define STBIR_PROFILE_FIRST_START( wh ) STBIR_PROFILE_FIRST_START_ll( split_info, wh ) +#define STBIR_PROFILE_CLEAR_EXTRAS() STBIR_PROFILE_CLEAR_EXTRAS_ll( split_info, split_count ) + +// for build data +#define STBIR_PROFILE_BUILD_START( wh ) STBIR_PROFILE_START_ll( profile_info, wh ) +#define STBIR_PROFILE_BUILD_END( wh ) STBIR_PROFILE_END_ll( profile_info, wh ) +#define STBIR_PROFILE_BUILD_FIRST_START( wh ) STBIR_PROFILE_FIRST_START_ll( profile_info, wh ) +#define STBIR_PROFILE_BUILD_CLEAR( info ) { int i; for(i=0;iprofile.array);i++) info->profile.array[i]=0; } + +#else // no profile + +#define STBIR_ONLY_PROFILE_GET_SPLIT_INFO +#define STBIR_ONLY_PROFILE_SET_SPLIT_INFO + +#define STBIR_ONLY_PROFILE_BUILD_GET_INFO +#define STBIR_ONLY_PROFILE_BUILD_SET_INFO + +#define STBIR_PROFILE_START( wh ) +#define STBIR_PROFILE_END( wh ) +#define STBIR_PROFILE_FIRST_START( wh ) +#define STBIR_PROFILE_CLEAR_EXTRAS( ) + +#define STBIR_PROFILE_BUILD_START( wh ) +#define STBIR_PROFILE_BUILD_END( wh ) +#define STBIR_PROFILE_BUILD_FIRST_START( wh ) +#define STBIR_PROFILE_BUILD_CLEAR( info ) + +#endif // stbir_profile + +#ifndef STBIR_CEILF +#include +#if _MSC_VER <= 1200 // support VC6 for Sean +#define STBIR_CEILF(x) ((float)ceil((float)(x))) +#define STBIR_FLOORF(x) ((float)floor((float)(x))) +#else +#define STBIR_CEILF(x) ceilf(x) +#define STBIR_FLOORF(x) floorf(x) +#endif +#endif + +#ifndef STBIR_MEMCPY +// For memcpy +#include +#define STBIR_MEMCPY( dest, src, len ) memcpy( dest, src, len ) +#endif + +#ifndef STBIR_SIMD + +// memcpy that is specically intentionally overlapping (src is smaller then dest, so can be +// a normal forward copy, bytes is divisible by 4 and bytes is greater than or equal to +// the diff between dest and src) +static void stbir_overlapping_memcpy( void * dest, void const * src, size_t bytes ) +{ + char STBIR_SIMD_STREAMOUT_PTR (*) sd = (char*) src; + char STBIR_SIMD_STREAMOUT_PTR( * ) s_end = ((char*) src) + bytes; + ptrdiff_t ofs_to_dest = (char*)dest - (char*)src; + + if ( ofs_to_dest >= 8 ) // is the overlap more than 8 away? + { + char STBIR_SIMD_STREAMOUT_PTR( * ) s_end8 = ((char*) src) + (bytes&~7); + do + { + STBIR_NO_UNROLL(sd); + *(stbir_uint64*)( sd + ofs_to_dest ) = *(stbir_uint64*) sd; + sd += 8; + } while ( sd < s_end8 ); + + if ( sd == s_end ) + return; + } + + do + { + STBIR_NO_UNROLL(sd); + *(int*)( sd + ofs_to_dest ) = *(int*) sd; + sd += 4; + } while ( sd < s_end ); +} + +#endif + +static float stbir__filter_trapezoid(float x, float scale, void * user_data) +{ + float halfscale = scale / 2; + float t = 0.5f + halfscale; + STBIR_ASSERT(scale <= 1); + STBIR__UNUSED(user_data); + + if ( x < 0.0f ) x = -x; + + if (x >= t) + return 0.0f; + else + { + float r = 0.5f - halfscale; + if (x <= r) + return 1.0f; + else + return (t - x) / scale; + } +} + +static float stbir__support_trapezoid(float scale, void * user_data) +{ + STBIR__UNUSED(user_data); + return 0.5f + scale / 2.0f; +} + +static float stbir__filter_triangle(float x, float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + + if ( x < 0.0f ) x = -x; + + if (x <= 1.0f) + return 1.0f - x; + else + return 0.0f; +} + +static float stbir__filter_point(float x, float s, void * user_data) +{ + STBIR__UNUSED(x); + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + + return 1.0f; +} + +static float stbir__filter_cubic(float x, float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + + if ( x < 0.0f ) x = -x; + + if (x < 1.0f) + return (4.0f + x*x*(3.0f*x - 6.0f))/6.0f; + else if (x < 2.0f) + return (8.0f + x*(-12.0f + x*(6.0f - x)))/6.0f; + + return (0.0f); +} + +static float stbir__filter_catmullrom(float x, float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + + if ( x < 0.0f ) x = -x; + + if (x < 1.0f) + return 1.0f - x*x*(2.5f - 1.5f*x); + else if (x < 2.0f) + return 2.0f - x*(4.0f + x*(0.5f*x - 2.5f)); + + return (0.0f); +} + +static float stbir__filter_mitchell(float x, float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + + if ( x < 0.0f ) x = -x; + + if (x < 1.0f) + return (16.0f + x*x*(21.0f * x - 36.0f))/18.0f; + else if (x < 2.0f) + return (32.0f + x*(-60.0f + x*(36.0f - 7.0f*x)))/18.0f; + + return (0.0f); +} + +static float stbir__support_zero(float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + return 0; +} + +static float stbir__support_zeropoint5(float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + return 0.5f; +} + +static float stbir__support_one(float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + return 1; +} + +static float stbir__support_two(float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + return 2; +} + +// This is the maximum number of input samples that can affect an output sample +// with the given filter from the output pixel's perspective +static int stbir__get_filter_pixel_width(stbir__support_callback * support, float scale, void * user_data) +{ + STBIR_ASSERT(support != 0); + + if ( scale >= ( 1.0f-stbir__small_float ) ) // upscale + return (int)STBIR_CEILF(support(1.0f/scale,user_data) * 2.0f); + else + return (int)STBIR_CEILF(support(scale,user_data) * 2.0f / scale); +} + +// this is how many coefficents per run of the filter (which is different +// from the filter_pixel_width depending on if we are scattering or gathering) +static int stbir__get_coefficient_width(stbir__sampler * samp, int is_gather, void * user_data) +{ + float scale = samp->scale_info.scale; + stbir__support_callback * support = samp->filter_support; + + switch( is_gather ) + { + case 1: + return (int)STBIR_CEILF(support(1.0f / scale, user_data) * 2.0f); + case 2: + return (int)STBIR_CEILF(support(scale, user_data) * 2.0f / scale); + case 0: + return (int)STBIR_CEILF(support(scale, user_data) * 2.0f); + default: + STBIR_ASSERT( (is_gather >= 0 ) && (is_gather <= 2 ) ); + return 0; + } +} + +static int stbir__get_contributors(stbir__sampler * samp, int is_gather) +{ + if (is_gather) + return samp->scale_info.output_sub_size; + else + return (samp->scale_info.input_full_size + samp->filter_pixel_margin * 2); +} + +static int stbir__edge_zero_full( int n, int max ) +{ + STBIR__UNUSED(n); + STBIR__UNUSED(max); + return 0; // NOTREACHED +} + +static int stbir__edge_clamp_full( int n, int max ) +{ + if (n < 0) + return 0; + + if (n >= max) + return max - 1; + + return n; // NOTREACHED +} + +static int stbir__edge_reflect_full( int n, int max ) +{ + if (n < 0) + { + if (n > -max) + return -n; + else + return max - 1; + } + + if (n >= max) + { + int max2 = max * 2; + if (n >= max2) + return 0; + else + return max2 - n - 1; + } + + return n; // NOTREACHED +} + +static int stbir__edge_wrap_full( int n, int max ) +{ + if (n >= 0) + return (n % max); + else + { + int m = (-n) % max; + + if (m != 0) + m = max - m; + + return (m); + } +} + +typedef int stbir__edge_wrap_func( int n, int max ); +static stbir__edge_wrap_func * stbir__edge_wrap_slow[] = +{ + stbir__edge_clamp_full, // STBIR_EDGE_CLAMP + stbir__edge_reflect_full, // STBIR_EDGE_REFLECT + stbir__edge_wrap_full, // STBIR_EDGE_WRAP + stbir__edge_zero_full, // STBIR_EDGE_ZERO +}; + +stbir__inline static int stbir__edge_wrap(stbir_edge edge, int n, int max) +{ + // avoid per-pixel switch + if (n >= 0 && n < max) + return n; + return stbir__edge_wrap_slow[edge]( n, max ); +} + +#define STBIR__MERGE_RUNS_PIXEL_THRESHOLD 16 + +// get information on the extents of a sampler +static void stbir__get_extents( stbir__sampler * samp, stbir__extents * scanline_extents ) +{ + int j, stop; + int left_margin, right_margin; + int min_n = 0x7fffffff, max_n = -0x7fffffff; + int min_left = 0x7fffffff, max_left = -0x7fffffff; + int min_right = 0x7fffffff, max_right = -0x7fffffff; + stbir_edge edge = samp->edge; + stbir__contributors* contributors = samp->contributors; + int output_sub_size = samp->scale_info.output_sub_size; + int input_full_size = samp->scale_info.input_full_size; + int filter_pixel_margin = samp->filter_pixel_margin; + + STBIR_ASSERT( samp->is_gather ); + + stop = output_sub_size; + for (j = 0; j < stop; j++ ) + { + STBIR_ASSERT( contributors[j].n1 >= contributors[j].n0 ); + if ( contributors[j].n0 < min_n ) + { + min_n = contributors[j].n0; + stop = j + filter_pixel_margin; // if we find a new min, only scan another filter width + if ( stop > output_sub_size ) stop = output_sub_size; + } + } + + stop = 0; + for (j = output_sub_size - 1; j >= stop; j-- ) + { + STBIR_ASSERT( contributors[j].n1 >= contributors[j].n0 ); + if ( contributors[j].n1 > max_n ) + { + max_n = contributors[j].n1; + stop = j - filter_pixel_margin; // if we find a new max, only scan another filter width + if (stop<0) stop = 0; + } + } + + STBIR_ASSERT( scanline_extents->conservative.n0 <= min_n ); + STBIR_ASSERT( scanline_extents->conservative.n1 >= max_n ); + + // now calculate how much into the margins we really read + left_margin = 0; + if ( min_n < 0 ) + { + left_margin = -min_n; + min_n = 0; + } + + right_margin = 0; + if ( max_n >= input_full_size ) + { + right_margin = max_n - input_full_size + 1; + max_n = input_full_size - 1; + } + + // index 1 is margin pixel extents (how many pixels we hang over the edge) + scanline_extents->edge_sizes[0] = left_margin; + scanline_extents->edge_sizes[1] = right_margin; + + // index 2 is pixels read from the input + scanline_extents->spans[0].n0 = min_n; + scanline_extents->spans[0].n1 = max_n; + scanline_extents->spans[0].pixel_offset_for_input = min_n; + + // default to no other input range + scanline_extents->spans[1].n0 = 0; + scanline_extents->spans[1].n1 = -1; + scanline_extents->spans[1].pixel_offset_for_input = 0; + + // don't have to do edge calc for zero clamp + if ( edge == STBIR_EDGE_ZERO ) + return; + + // convert margin pixels to the pixels within the input (min and max) + for( j = -left_margin ; j < 0 ; j++ ) + { + int p = stbir__edge_wrap( edge, j, input_full_size ); + if ( p < min_left ) + min_left = p; + if ( p > max_left ) + max_left = p; + } + + for( j = input_full_size ; j < (input_full_size + right_margin) ; j++ ) + { + int p = stbir__edge_wrap( edge, j, input_full_size ); + if ( p < min_right ) + min_right = p; + if ( p > max_right ) + max_right = p; + } + + // merge the left margin pixel region if it connects within 4 pixels of main pixel region + if ( min_left != 0x7fffffff ) + { + if ( ( ( min_left <= min_n ) && ( ( max_left + STBIR__MERGE_RUNS_PIXEL_THRESHOLD ) >= min_n ) ) || + ( ( min_n <= min_left ) && ( ( max_n + STBIR__MERGE_RUNS_PIXEL_THRESHOLD ) >= max_left ) ) ) + { + scanline_extents->spans[0].n0 = min_n = stbir__min( min_n, min_left ); + scanline_extents->spans[0].n1 = max_n = stbir__max( max_n, max_left ); + scanline_extents->spans[0].pixel_offset_for_input = min_n; + left_margin = 0; + } + } + + // merge the right margin pixel region if it connects within 4 pixels of main pixel region + if ( min_right != 0x7fffffff ) + { + if ( ( ( min_right <= min_n ) && ( ( max_right + STBIR__MERGE_RUNS_PIXEL_THRESHOLD ) >= min_n ) ) || + ( ( min_n <= min_right ) && ( ( max_n + STBIR__MERGE_RUNS_PIXEL_THRESHOLD ) >= max_right ) ) ) + { + scanline_extents->spans[0].n0 = min_n = stbir__min( min_n, min_right ); + scanline_extents->spans[0].n1 = max_n = stbir__max( max_n, max_right ); + scanline_extents->spans[0].pixel_offset_for_input = min_n; + right_margin = 0; + } + } + + STBIR_ASSERT( scanline_extents->conservative.n0 <= min_n ); + STBIR_ASSERT( scanline_extents->conservative.n1 >= max_n ); + + // you get two ranges when you have the WRAP edge mode and you are doing just the a piece of the resize + // so you need to get a second run of pixels from the opposite side of the scanline (which you + // wouldn't need except for WRAP) + + + // if we can't merge the min_left range, add it as a second range + if ( ( left_margin ) && ( min_left != 0x7fffffff ) ) + { + stbir__span * newspan = scanline_extents->spans + 1; + STBIR_ASSERT( right_margin == 0 ); + if ( min_left < scanline_extents->spans[0].n0 ) + { + scanline_extents->spans[1].pixel_offset_for_input = scanline_extents->spans[0].n0; + scanline_extents->spans[1].n0 = scanline_extents->spans[0].n0; + scanline_extents->spans[1].n1 = scanline_extents->spans[0].n1; + --newspan; + } + newspan->pixel_offset_for_input = min_left; + newspan->n0 = -left_margin; + newspan->n1 = ( max_left - min_left ) - left_margin; + scanline_extents->edge_sizes[0] = 0; // don't need to copy the left margin, since we are directly decoding into the margin + return; + } + + // if we can't merge the min_left range, add it as a second range + if ( ( right_margin ) && ( min_right != 0x7fffffff ) ) + { + stbir__span * newspan = scanline_extents->spans + 1; + if ( min_right < scanline_extents->spans[0].n0 ) + { + scanline_extents->spans[1].pixel_offset_for_input = scanline_extents->spans[0].n0; + scanline_extents->spans[1].n0 = scanline_extents->spans[0].n0; + scanline_extents->spans[1].n1 = scanline_extents->spans[0].n1; + --newspan; + } + newspan->pixel_offset_for_input = min_right; + newspan->n0 = scanline_extents->spans[1].n1 + 1; + newspan->n1 = scanline_extents->spans[1].n1 + 1 + ( max_right - min_right ); + scanline_extents->edge_sizes[1] = 0; // don't need to copy the right margin, since we are directly decoding into the margin + return; + } +} + +static void stbir__calculate_in_pixel_range( int * first_pixel, int * last_pixel, float out_pixel_center, float out_filter_radius, float inv_scale, float out_shift, int input_size, stbir_edge edge ) +{ + int first, last; + float out_pixel_influence_lowerbound = out_pixel_center - out_filter_radius; + float out_pixel_influence_upperbound = out_pixel_center + out_filter_radius; + + float in_pixel_influence_lowerbound = (out_pixel_influence_lowerbound + out_shift) * inv_scale; + float in_pixel_influence_upperbound = (out_pixel_influence_upperbound + out_shift) * inv_scale; + + first = (int)(STBIR_FLOORF(in_pixel_influence_lowerbound + 0.5f)); + last = (int)(STBIR_FLOORF(in_pixel_influence_upperbound - 0.5f)); + + if ( edge == STBIR_EDGE_WRAP ) + { + if ( first < -input_size ) + first = -input_size; + if ( last >= (input_size*2)) + last = (input_size*2) - 1; + } + + *first_pixel = first; + *last_pixel = last; +} + +static void stbir__calculate_coefficients_for_gather_upsample( float out_filter_radius, stbir__kernel_callback * kernel, stbir__scale_info * scale_info, int num_contributors, stbir__contributors* contributors, float* coefficient_group, int coefficient_width, stbir_edge edge, void * user_data ) +{ + int n, end; + float inv_scale = scale_info->inv_scale; + float out_shift = scale_info->pixel_shift; + int input_size = scale_info->input_full_size; + int numerator = scale_info->scale_numerator; + int polyphase = ( ( scale_info->scale_is_rational ) && ( numerator < num_contributors ) ); + + // Looping through out pixels + end = num_contributors; if ( polyphase ) end = numerator; + for (n = 0; n < end; n++) + { + int i; + int last_non_zero; + float out_pixel_center = (float)n + 0.5f; + float in_center_of_out = (out_pixel_center + out_shift) * inv_scale; + + int in_first_pixel, in_last_pixel; + + stbir__calculate_in_pixel_range( &in_first_pixel, &in_last_pixel, out_pixel_center, out_filter_radius, inv_scale, out_shift, input_size, edge ); + + last_non_zero = -1; + for (i = 0; i <= in_last_pixel - in_first_pixel; i++) + { + float in_pixel_center = (float)(i + in_first_pixel) + 0.5f; + float coeff = kernel(in_center_of_out - in_pixel_center, inv_scale, user_data); + + // kill denormals + if ( ( ( coeff < stbir__small_float ) && ( coeff > -stbir__small_float ) ) ) + { + if ( i == 0 ) // if we're at the front, just eat zero contributors + { + STBIR_ASSERT ( ( in_last_pixel - in_first_pixel ) != 0 ); // there should be at least one contrib + ++in_first_pixel; + i--; + continue; + } + coeff = 0; // make sure is fully zero (should keep denormals away) + } + else + last_non_zero = i; + + coefficient_group[i] = coeff; + } + + in_last_pixel = last_non_zero+in_first_pixel; // kills trailing zeros + contributors->n0 = in_first_pixel; + contributors->n1 = in_last_pixel; + + STBIR_ASSERT(contributors->n1 >= contributors->n0); + + ++contributors; + coefficient_group += coefficient_width; + } +} + +static void stbir__insert_coeff( stbir__contributors * contribs, float * coeffs, int new_pixel, float new_coeff ) +{ + if ( new_pixel <= contribs->n1 ) // before the end + { + if ( new_pixel < contribs->n0 ) // before the front? + { + int j, o = contribs->n0 - new_pixel; + for ( j = contribs->n1 - contribs->n0 ; j <= 0 ; j-- ) + coeffs[ j + o ] = coeffs[ j ]; + for ( j = 1 ; j < o ; j-- ) + coeffs[ j ] = coeffs[ 0 ]; + coeffs[ 0 ] = new_coeff; + contribs->n0 = new_pixel; + } + else + { + coeffs[ new_pixel - contribs->n0 ] += new_coeff; + } + } + else + { + int j, e = new_pixel - contribs->n0; + for( j = ( contribs->n1 - contribs->n0 ) + 1 ; j < e ; j++ ) // clear in-betweens coeffs if there are any + coeffs[j] = 0; + + coeffs[ e ] = new_coeff; + contribs->n1 = new_pixel; + } +} + +static void stbir__calculate_out_pixel_range( int * first_pixel, int * last_pixel, float in_pixel_center, float in_pixels_radius, float scale, float out_shift, int out_size ) +{ + float in_pixel_influence_lowerbound = in_pixel_center - in_pixels_radius; + float in_pixel_influence_upperbound = in_pixel_center + in_pixels_radius; + float out_pixel_influence_lowerbound = in_pixel_influence_lowerbound * scale - out_shift; + float out_pixel_influence_upperbound = in_pixel_influence_upperbound * scale - out_shift; + int out_first_pixel = (int)(STBIR_FLOORF(out_pixel_influence_lowerbound + 0.5f)); + int out_last_pixel = (int)(STBIR_FLOORF(out_pixel_influence_upperbound - 0.5f)); + + if ( out_first_pixel < 0 ) + out_first_pixel = 0; + if ( out_last_pixel >= out_size ) + out_last_pixel = out_size - 1; + *first_pixel = out_first_pixel; + *last_pixel = out_last_pixel; +} + +static void stbir__calculate_coefficients_for_gather_downsample( int start, int end, float in_pixels_radius, stbir__kernel_callback * kernel, stbir__scale_info * scale_info, int coefficient_width, int num_contributors, stbir__contributors * contributors, float * coefficient_group, void * user_data ) +{ + int in_pixel; + int i; + int first_out_inited = -1; + float scale = scale_info->scale; + float out_shift = scale_info->pixel_shift; + int out_size = scale_info->output_sub_size; + int numerator = scale_info->scale_numerator; + int polyphase = ( ( scale_info->scale_is_rational ) && ( numerator < out_size ) ); + + STBIR__UNUSED(num_contributors); + + // Loop through the input pixels + for (in_pixel = start; in_pixel < end; in_pixel++) + { + float in_pixel_center = (float)in_pixel + 0.5f; + float out_center_of_in = in_pixel_center * scale - out_shift; + int out_first_pixel, out_last_pixel; + + stbir__calculate_out_pixel_range( &out_first_pixel, &out_last_pixel, in_pixel_center, in_pixels_radius, scale, out_shift, out_size ); + + if ( out_first_pixel > out_last_pixel ) + continue; + + // clamp or exit if we are using polyphase filtering, and the limit is up + if ( polyphase ) + { + // when polyphase, you only have to do coeffs up to the numerator count + if ( out_first_pixel == numerator ) + break; + + // don't do any extra work, clamp last pixel at numerator too + if ( out_last_pixel >= numerator ) + out_last_pixel = numerator - 1; + } + + for (i = 0; i <= out_last_pixel - out_first_pixel; i++) + { + float out_pixel_center = (float)(i + out_first_pixel) + 0.5f; + float x = out_pixel_center - out_center_of_in; + float coeff = kernel(x, scale, user_data) * scale; + + // kill the coeff if it's too small (avoid denormals) + if ( ( ( coeff < stbir__small_float ) && ( coeff > -stbir__small_float ) ) ) + coeff = 0.0f; + + { + int out = i + out_first_pixel; + float * coeffs = coefficient_group + out * coefficient_width; + stbir__contributors * contribs = contributors + out; + + // is this the first time this output pixel has been seen? Init it. + if ( out > first_out_inited ) + { + STBIR_ASSERT( out == ( first_out_inited + 1 ) ); // ensure we have only advanced one at time + first_out_inited = out; + contribs->n0 = in_pixel; + contribs->n1 = in_pixel; + coeffs[0] = coeff; + } + else + { + // insert on end (always in order) + if ( coeffs[0] == 0.0f ) // if the first coefficent is zero, then zap it for this coeffs + { + STBIR_ASSERT( ( in_pixel - contribs->n0 ) == 1 ); // ensure that when we zap, we're at the 2nd pos + contribs->n0 = in_pixel; + } + contribs->n1 = in_pixel; + STBIR_ASSERT( ( in_pixel - contribs->n0 ) < coefficient_width ); + coeffs[in_pixel - contribs->n0] = coeff; + } + } + } + } +} + +#ifdef STBIR_RENORMALIZE_IN_FLOAT +#define STBIR_RENORM_TYPE float +#else +#define STBIR_RENORM_TYPE double +#endif + +static void stbir__cleanup_gathered_coefficients( stbir_edge edge, stbir__filter_extent_info* filter_info, stbir__scale_info * scale_info, int num_contributors, stbir__contributors* contributors, float * coefficient_group, int coefficient_width ) +{ + int input_size = scale_info->input_full_size; + int input_last_n1 = input_size - 1; + int n, end; + int lowest = 0x7fffffff; + int highest = -0x7fffffff; + int widest = -1; + int numerator = scale_info->scale_numerator; + int denominator = scale_info->scale_denominator; + int polyphase = ( ( scale_info->scale_is_rational ) && ( numerator < num_contributors ) ); + float * coeffs; + stbir__contributors * contribs; + + // weight all the coeffs for each sample + coeffs = coefficient_group; + contribs = contributors; + end = num_contributors; if ( polyphase ) end = numerator; + for (n = 0; n < end; n++) + { + int i; + STBIR_RENORM_TYPE filter_scale, total_filter = 0; + int e; + + // add all contribs + e = contribs->n1 - contribs->n0; + for( i = 0 ; i <= e ; i++ ) + { + total_filter += (STBIR_RENORM_TYPE) coeffs[i]; + STBIR_ASSERT( ( coeffs[i] >= -2.0f ) && ( coeffs[i] <= 2.0f ) ); // check for wonky weights + } + + // rescale + if ( ( total_filter < stbir__small_float ) && ( total_filter > -stbir__small_float ) ) + { + // all coeffs are extremely small, just zero it + contribs->n1 = contribs->n0; + coeffs[0] = 0.0f; + } + else + { + // if the total isn't 1.0, rescale everything + if ( ( total_filter < (1.0f-stbir__small_float) ) || ( total_filter > (1.0f+stbir__small_float) ) ) + { + filter_scale = ((STBIR_RENORM_TYPE)1.0) / total_filter; + + // scale them all + for (i = 0; i <= e; i++) + coeffs[i] = (float) ( coeffs[i] * filter_scale ); + } + } + ++contribs; + coeffs += coefficient_width; + } + + // if we have a rational for the scale, we can exploit the polyphaseness to not calculate + // most of the coefficients, so we copy them here + if ( polyphase ) + { + stbir__contributors * prev_contribs = contributors; + stbir__contributors * cur_contribs = contributors + numerator; + + for( n = numerator ; n < num_contributors ; n++ ) + { + cur_contribs->n0 = prev_contribs->n0 + denominator; + cur_contribs->n1 = prev_contribs->n1 + denominator; + ++cur_contribs; + ++prev_contribs; + } + stbir_overlapping_memcpy( coefficient_group + numerator * coefficient_width, coefficient_group, ( num_contributors - numerator ) * coefficient_width * sizeof( coeffs[ 0 ] ) ); + } + + coeffs = coefficient_group; + contribs = contributors; + for (n = 0; n < num_contributors; n++) + { + int i; + + // in zero edge mode, just remove out of bounds contribs completely (since their weights are accounted for now) + if ( edge == STBIR_EDGE_ZERO ) + { + // shrink the right side if necessary + if ( contribs->n1 > input_last_n1 ) + contribs->n1 = input_last_n1; + + // shrink the left side + if ( contribs->n0 < 0 ) + { + int j, left, skips = 0; + + skips = -contribs->n0; + contribs->n0 = 0; + + // now move down the weights + left = contribs->n1 - contribs->n0 + 1; + if ( left > 0 ) + { + for( j = 0 ; j < left ; j++ ) + coeffs[ j ] = coeffs[ j + skips ]; + } + } + } + else if ( ( edge == STBIR_EDGE_CLAMP ) || ( edge == STBIR_EDGE_REFLECT ) ) + { + // for clamp and reflect, calculate the true inbounds position (based on edge type) and just add that to the existing weight + + // right hand side first + if ( contribs->n1 > input_last_n1 ) + { + int start = contribs->n0; + int endi = contribs->n1; + contribs->n1 = input_last_n1; + for( i = input_size; i <= endi; i++ ) + stbir__insert_coeff( contribs, coeffs, stbir__edge_wrap_slow[edge]( i, input_size ), coeffs[i-start] ); + } + + // now check left hand edge + if ( contribs->n0 < 0 ) + { + int save_n0; + float save_n0_coeff; + float * c = coeffs - ( contribs->n0 + 1 ); + + // reinsert the coeffs with it reflected or clamped (insert accumulates, if the coeffs exist) + for( i = -1 ; i > contribs->n0 ; i-- ) + stbir__insert_coeff( contribs, coeffs, stbir__edge_wrap_slow[edge]( i, input_size ), *c-- ); + save_n0 = contribs->n0; + save_n0_coeff = c[0]; // save it, since we didn't do the final one (i==n0), because there might be too many coeffs to hold (before we resize)! + + // now slide all the coeffs down (since we have accumulated them in the positive contribs) and reset the first contrib + contribs->n0 = 0; + for(i = 0 ; i <= contribs->n1 ; i++ ) + coeffs[i] = coeffs[i-save_n0]; + + // now that we have shrunk down the contribs, we insert the first one safely + stbir__insert_coeff( contribs, coeffs, stbir__edge_wrap_slow[edge]( save_n0, input_size ), save_n0_coeff ); + } + } + + if ( contribs->n0 <= contribs->n1 ) + { + int diff = contribs->n1 - contribs->n0 + 1; + while ( diff && ( coeffs[ diff-1 ] == 0.0f ) ) + --diff; + contribs->n1 = contribs->n0 + diff - 1; + + if ( contribs->n0 <= contribs->n1 ) + { + if ( contribs->n0 < lowest ) + lowest = contribs->n0; + if ( contribs->n1 > highest ) + highest = contribs->n1; + if ( diff > widest ) + widest = diff; + } + + // re-zero out unused coefficients (if any) + for( i = diff ; i < coefficient_width ; i++ ) + coeffs[i] = 0.0f; + } + + ++contribs; + coeffs += coefficient_width; + } + filter_info->lowest = lowest; + filter_info->highest = highest; + filter_info->widest = widest; +} + +#undef STBIR_RENORM_TYPE + +static int stbir__pack_coefficients( int num_contributors, stbir__contributors* contributors, float * coefficents, int coefficient_width, int widest, int row0, int row1 ) +{ + #define STBIR_MOVE_1( dest, src ) { STBIR_NO_UNROLL(dest); ((stbir_uint32*)(dest))[0] = ((stbir_uint32*)(src))[0]; } + #define STBIR_MOVE_2( dest, src ) { STBIR_NO_UNROLL(dest); ((stbir_uint64*)(dest))[0] = ((stbir_uint64*)(src))[0]; } + #ifdef STBIR_SIMD + #define STBIR_MOVE_4( dest, src ) { stbir__simdf t; STBIR_NO_UNROLL(dest); stbir__simdf_load( t, src ); stbir__simdf_store( dest, t ); } + #else + #define STBIR_MOVE_4( dest, src ) { STBIR_NO_UNROLL(dest); ((stbir_uint64*)(dest))[0] = ((stbir_uint64*)(src))[0]; ((stbir_uint64*)(dest))[1] = ((stbir_uint64*)(src))[1]; } + #endif + + int row_end = row1 + 1; + STBIR__UNUSED( row0 ); // only used in an assert + + if ( coefficient_width != widest ) + { + float * pc = coefficents; + float * coeffs = coefficents; + float * pc_end = coefficents + num_contributors * widest; + switch( widest ) + { + case 1: + do { + STBIR_MOVE_1( pc, coeffs ); + ++pc; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 2: + do { + STBIR_MOVE_2( pc, coeffs ); + pc += 2; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 3: + do { + STBIR_MOVE_2( pc, coeffs ); + STBIR_MOVE_1( pc+2, coeffs+2 ); + pc += 3; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 4: + do { + STBIR_MOVE_4( pc, coeffs ); + pc += 4; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 5: + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_1( pc+4, coeffs+4 ); + pc += 5; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 6: + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_2( pc+4, coeffs+4 ); + pc += 6; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 7: + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_2( pc+4, coeffs+4 ); + STBIR_MOVE_1( pc+6, coeffs+6 ); + pc += 7; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 8: + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_4( pc+4, coeffs+4 ); + pc += 8; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 9: + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_4( pc+4, coeffs+4 ); + STBIR_MOVE_1( pc+8, coeffs+8 ); + pc += 9; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 10: + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_4( pc+4, coeffs+4 ); + STBIR_MOVE_2( pc+8, coeffs+8 ); + pc += 10; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 11: + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_4( pc+4, coeffs+4 ); + STBIR_MOVE_2( pc+8, coeffs+8 ); + STBIR_MOVE_1( pc+10, coeffs+10 ); + pc += 11; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 12: + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_4( pc+4, coeffs+4 ); + STBIR_MOVE_4( pc+8, coeffs+8 ); + pc += 12; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + default: + do { + float * copy_end = pc + widest - 4; + float * c = coeffs; + do { + STBIR_NO_UNROLL( pc ); + STBIR_MOVE_4( pc, c ); + pc += 4; + c += 4; + } while ( pc <= copy_end ); + copy_end += 4; + while ( pc < copy_end ) + { + STBIR_MOVE_1( pc, c ); + ++pc; ++c; + } + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + } + } + + // some horizontal routines read one float off the end (which is then masked off), so put in a sentinal so we don't read an snan or denormal + coefficents[ widest * num_contributors ] = 8888.0f; + + // the minimum we might read for unrolled filters widths is 12. So, we need to + // make sure we never read outside the decode buffer, by possibly moving + // the sample area back into the scanline, and putting zeros weights first. + // we start on the right edge and check until we're well past the possible + // clip area (2*widest). + { + stbir__contributors * contribs = contributors + num_contributors - 1; + float * coeffs = coefficents + widest * ( num_contributors - 1 ); + + // go until no chance of clipping (this is usually less than 8 lops) + while ( ( contribs >= contributors ) && ( ( contribs->n0 + widest*2 ) >= row_end ) ) + { + // might we clip?? + if ( ( contribs->n0 + widest ) > row_end ) + { + int stop_range = widest; + + // if range is larger than 12, it will be handled by generic loops that can terminate on the exact length + // of this contrib n1, instead of a fixed widest amount - so calculate this + if ( widest > 12 ) + { + int mod; + + // how far will be read in the n_coeff loop (which depends on the widest count mod4); + mod = widest & 3; + stop_range = ( ( ( contribs->n1 - contribs->n0 + 1 ) - mod + 3 ) & ~3 ) + mod; + + // the n_coeff loops do a minimum amount of coeffs, so factor that in! + if ( stop_range < ( 8 + mod ) ) stop_range = 8 + mod; + } + + // now see if we still clip with the refined range + if ( ( contribs->n0 + stop_range ) > row_end ) + { + int new_n0 = row_end - stop_range; + int num = contribs->n1 - contribs->n0 + 1; + int backup = contribs->n0 - new_n0; + float * from_co = coeffs + num - 1; + float * to_co = from_co + backup; + + STBIR_ASSERT( ( new_n0 >= row0 ) && ( new_n0 < contribs->n0 ) ); + + // move the coeffs over + while( num ) + { + *to_co-- = *from_co--; + --num; + } + // zero new positions + while ( to_co >= coeffs ) + *to_co-- = 0; + // set new start point + contribs->n0 = new_n0; + if ( widest > 12 ) + { + int mod; + + // how far will be read in the n_coeff loop (which depends on the widest count mod4); + mod = widest & 3; + stop_range = ( ( ( contribs->n1 - contribs->n0 + 1 ) - mod + 3 ) & ~3 ) + mod; + + // the n_coeff loops do a minimum amount of coeffs, so factor that in! + if ( stop_range < ( 8 + mod ) ) stop_range = 8 + mod; + } + } + } + --contribs; + coeffs -= widest; + } + } + + return widest; + #undef STBIR_MOVE_1 + #undef STBIR_MOVE_2 + #undef STBIR_MOVE_4 +} + +static void stbir__calculate_filters( stbir__sampler * samp, stbir__sampler * other_axis_for_pivot, void * user_data STBIR_ONLY_PROFILE_BUILD_GET_INFO ) +{ + int n; + float scale = samp->scale_info.scale; + stbir__kernel_callback * kernel = samp->filter_kernel; + stbir__support_callback * support = samp->filter_support; + float inv_scale = samp->scale_info.inv_scale; + int input_full_size = samp->scale_info.input_full_size; + int gather_num_contributors = samp->num_contributors; + stbir__contributors* gather_contributors = samp->contributors; + float * gather_coeffs = samp->coefficients; + int gather_coefficient_width = samp->coefficient_width; + + switch ( samp->is_gather ) + { + case 1: // gather upsample + { + float out_pixels_radius = support(inv_scale,user_data) * scale; + + stbir__calculate_coefficients_for_gather_upsample( out_pixels_radius, kernel, &samp->scale_info, gather_num_contributors, gather_contributors, gather_coeffs, gather_coefficient_width, samp->edge, user_data ); + + STBIR_PROFILE_BUILD_START( cleanup ); + stbir__cleanup_gathered_coefficients( samp->edge, &samp->extent_info, &samp->scale_info, gather_num_contributors, gather_contributors, gather_coeffs, gather_coefficient_width ); + STBIR_PROFILE_BUILD_END( cleanup ); + } + break; + + case 0: // scatter downsample (only on vertical) + case 2: // gather downsample + { + float in_pixels_radius = support(scale,user_data) * inv_scale; + int filter_pixel_margin = samp->filter_pixel_margin; + int input_end = input_full_size + filter_pixel_margin; + + // if this is a scatter, we do a downsample gather to get the coeffs, and then pivot after + if ( !samp->is_gather ) + { + // check if we are using the same gather downsample on the horizontal as this vertical, + // if so, then we don't have to generate them, we can just pivot from the horizontal. + if ( other_axis_for_pivot ) + { + gather_contributors = other_axis_for_pivot->contributors; + gather_coeffs = other_axis_for_pivot->coefficients; + gather_coefficient_width = other_axis_for_pivot->coefficient_width; + gather_num_contributors = other_axis_for_pivot->num_contributors; + samp->extent_info.lowest = other_axis_for_pivot->extent_info.lowest; + samp->extent_info.highest = other_axis_for_pivot->extent_info.highest; + samp->extent_info.widest = other_axis_for_pivot->extent_info.widest; + goto jump_right_to_pivot; + } + + gather_contributors = samp->gather_prescatter_contributors; + gather_coeffs = samp->gather_prescatter_coefficients; + gather_coefficient_width = samp->gather_prescatter_coefficient_width; + gather_num_contributors = samp->gather_prescatter_num_contributors; + } + + stbir__calculate_coefficients_for_gather_downsample( -filter_pixel_margin, input_end, in_pixels_radius, kernel, &samp->scale_info, gather_coefficient_width, gather_num_contributors, gather_contributors, gather_coeffs, user_data ); + + STBIR_PROFILE_BUILD_START( cleanup ); + stbir__cleanup_gathered_coefficients( samp->edge, &samp->extent_info, &samp->scale_info, gather_num_contributors, gather_contributors, gather_coeffs, gather_coefficient_width ); + STBIR_PROFILE_BUILD_END( cleanup ); + + if ( !samp->is_gather ) + { + // if this is a scatter (vertical only), then we need to pivot the coeffs + stbir__contributors * scatter_contributors; + int highest_set; + + jump_right_to_pivot: + + STBIR_PROFILE_BUILD_START( pivot ); + + highest_set = (-filter_pixel_margin) - 1; + for (n = 0; n < gather_num_contributors; n++) + { + int k; + int gn0 = gather_contributors->n0, gn1 = gather_contributors->n1; + int scatter_coefficient_width = samp->coefficient_width; + float * scatter_coeffs = samp->coefficients + ( gn0 + filter_pixel_margin ) * scatter_coefficient_width; + float * g_coeffs = gather_coeffs; + scatter_contributors = samp->contributors + ( gn0 + filter_pixel_margin ); + + for (k = gn0 ; k <= gn1 ; k++ ) + { + float gc = *g_coeffs++; + + // skip zero and denormals - must skip zeros to avoid adding coeffs beyond scatter_coefficient_width + // (which happens when pivoting from horizontal, which might have dummy zeros) + if ( ( ( gc >= stbir__small_float ) || ( gc <= -stbir__small_float ) ) ) + { + if ( ( k > highest_set ) || ( scatter_contributors->n0 > scatter_contributors->n1 ) ) + { + { + // if we are skipping over several contributors, we need to clear the skipped ones + stbir__contributors * clear_contributors = samp->contributors + ( highest_set + filter_pixel_margin + 1); + while ( clear_contributors < scatter_contributors ) + { + clear_contributors->n0 = 0; + clear_contributors->n1 = -1; + ++clear_contributors; + } + } + scatter_contributors->n0 = n; + scatter_contributors->n1 = n; + scatter_coeffs[0] = gc; + highest_set = k; + } + else + { + stbir__insert_coeff( scatter_contributors, scatter_coeffs, n, gc ); + } + STBIR_ASSERT( ( scatter_contributors->n1 - scatter_contributors->n0 + 1 ) <= scatter_coefficient_width ); + } + ++scatter_contributors; + scatter_coeffs += scatter_coefficient_width; + } + + ++gather_contributors; + gather_coeffs += gather_coefficient_width; + } + + // now clear any unset contribs + { + stbir__contributors * clear_contributors = samp->contributors + ( highest_set + filter_pixel_margin + 1); + stbir__contributors * end_contributors = samp->contributors + samp->num_contributors; + while ( clear_contributors < end_contributors ) + { + clear_contributors->n0 = 0; + clear_contributors->n1 = -1; + ++clear_contributors; + } + } + + STBIR_PROFILE_BUILD_END( pivot ); + } + } + break; + } +} + + +//======================================================================================================== +// scanline decoders and encoders + +#define stbir__coder_min_num 1 +#define STB_IMAGE_RESIZE_DO_CODERS +#include STBIR__HEADER_FILENAME + +#define stbir__decode_suffix BGRA +#define stbir__decode_swizzle +#define stbir__decode_order0 2 +#define stbir__decode_order1 1 +#define stbir__decode_order2 0 +#define stbir__decode_order3 3 +#define stbir__encode_order0 2 +#define stbir__encode_order1 1 +#define stbir__encode_order2 0 +#define stbir__encode_order3 3 +#define stbir__coder_min_num 4 +#define STB_IMAGE_RESIZE_DO_CODERS +#include STBIR__HEADER_FILENAME + +#define stbir__decode_suffix ARGB +#define stbir__decode_swizzle +#define stbir__decode_order0 1 +#define stbir__decode_order1 2 +#define stbir__decode_order2 3 +#define stbir__decode_order3 0 +#define stbir__encode_order0 3 +#define stbir__encode_order1 0 +#define stbir__encode_order2 1 +#define stbir__encode_order3 2 +#define stbir__coder_min_num 4 +#define STB_IMAGE_RESIZE_DO_CODERS +#include STBIR__HEADER_FILENAME + +#define stbir__decode_suffix ABGR +#define stbir__decode_swizzle +#define stbir__decode_order0 3 +#define stbir__decode_order1 2 +#define stbir__decode_order2 1 +#define stbir__decode_order3 0 +#define stbir__encode_order0 3 +#define stbir__encode_order1 2 +#define stbir__encode_order2 1 +#define stbir__encode_order3 0 +#define stbir__coder_min_num 4 +#define STB_IMAGE_RESIZE_DO_CODERS +#include STBIR__HEADER_FILENAME + +#define stbir__decode_suffix AR +#define stbir__decode_swizzle +#define stbir__decode_order0 1 +#define stbir__decode_order1 0 +#define stbir__decode_order2 3 +#define stbir__decode_order3 2 +#define stbir__encode_order0 1 +#define stbir__encode_order1 0 +#define stbir__encode_order2 3 +#define stbir__encode_order3 2 +#define stbir__coder_min_num 2 +#define STB_IMAGE_RESIZE_DO_CODERS +#include STBIR__HEADER_FILENAME + + +// fancy alpha means we expand to keep both premultipied and non-premultiplied color channels +static void stbir__fancy_alpha_weight_4ch( float * out_buffer, int width_times_channels ) +{ + float STBIR_STREAMOUT_PTR(*) out = out_buffer; + float const * end_decode = out_buffer + ( width_times_channels / 4 ) * 7; // decode buffer aligned to end of out_buffer + float STBIR_STREAMOUT_PTR(*) decode = (float*)end_decode - width_times_channels; + + // fancy alpha is stored internally as R G B A Rpm Gpm Bpm + + #ifdef STBIR_SIMD + + #ifdef STBIR_SIMD8 + decode += 16; + while ( decode <= end_decode ) + { + stbir__simdf8 d0,d1,a0,a1,p0,p1; + STBIR_NO_UNROLL(decode); + stbir__simdf8_load( d0, decode-16 ); + stbir__simdf8_load( d1, decode-16+8 ); + stbir__simdf8_0123to33333333( a0, d0 ); + stbir__simdf8_0123to33333333( a1, d1 ); + stbir__simdf8_mult( p0, a0, d0 ); + stbir__simdf8_mult( p1, a1, d1 ); + stbir__simdf8_bot4s( a0, d0, p0 ); + stbir__simdf8_bot4s( a1, d1, p1 ); + stbir__simdf8_top4s( d0, d0, p0 ); + stbir__simdf8_top4s( d1, d1, p1 ); + stbir__simdf8_store ( out, a0 ); + stbir__simdf8_store ( out+7, d0 ); + stbir__simdf8_store ( out+14, a1 ); + stbir__simdf8_store ( out+21, d1 ); + decode += 16; + out += 28; + } + decode -= 16; + #else + decode += 8; + while ( decode <= end_decode ) + { + stbir__simdf d0,a0,d1,a1,p0,p1; + STBIR_NO_UNROLL(decode); + stbir__simdf_load( d0, decode-8 ); + stbir__simdf_load( d1, decode-8+4 ); + stbir__simdf_0123to3333( a0, d0 ); + stbir__simdf_0123to3333( a1, d1 ); + stbir__simdf_mult( p0, a0, d0 ); + stbir__simdf_mult( p1, a1, d1 ); + stbir__simdf_store ( out, d0 ); + stbir__simdf_store ( out+4, p0 ); + stbir__simdf_store ( out+7, d1 ); + stbir__simdf_store ( out+7+4, p1 ); + decode += 8; + out += 14; + } + decode -= 8; + #endif + + // might be one last odd pixel + #ifdef STBIR_SIMD8 + while ( decode < end_decode ) + #else + if ( decode < end_decode ) + #endif + { + stbir__simdf d,a,p; + stbir__simdf_load( d, decode ); + stbir__simdf_0123to3333( a, d ); + stbir__simdf_mult( p, a, d ); + stbir__simdf_store ( out, d ); + stbir__simdf_store ( out+4, p ); + decode += 4; + out += 7; + } + + #else + + while( decode < end_decode ) + { + float r = decode[0], g = decode[1], b = decode[2], alpha = decode[3]; + out[0] = r; + out[1] = g; + out[2] = b; + out[3] = alpha; + out[4] = r * alpha; + out[5] = g * alpha; + out[6] = b * alpha; + out += 7; + decode += 4; + } + + #endif +} + +static void stbir__fancy_alpha_weight_2ch( float * out_buffer, int width_times_channels ) +{ + float STBIR_STREAMOUT_PTR(*) out = out_buffer; + float const * end_decode = out_buffer + ( width_times_channels / 2 ) * 3; + float STBIR_STREAMOUT_PTR(*) decode = (float*)end_decode - width_times_channels; + + // for fancy alpha, turns into: [X A Xpm][X A Xpm],etc + + #ifdef STBIR_SIMD + + decode += 8; + if ( decode <= end_decode ) + { + do { + #ifdef STBIR_SIMD8 + stbir__simdf8 d0,a0,p0; + STBIR_NO_UNROLL(decode); + stbir__simdf8_load( d0, decode-8 ); + stbir__simdf8_0123to11331133( p0, d0 ); + stbir__simdf8_0123to00220022( a0, d0 ); + stbir__simdf8_mult( p0, p0, a0 ); + + stbir__simdf_store2( out, stbir__if_simdf8_cast_to_simdf4( d0 ) ); + stbir__simdf_store( out+2, stbir__if_simdf8_cast_to_simdf4( p0 ) ); + stbir__simdf_store2h( out+3, stbir__if_simdf8_cast_to_simdf4( d0 ) ); + + stbir__simdf_store2( out+6, stbir__simdf8_gettop4( d0 ) ); + stbir__simdf_store( out+8, stbir__simdf8_gettop4( p0 ) ); + stbir__simdf_store2h( out+9, stbir__simdf8_gettop4( d0 ) ); + #else + stbir__simdf d0,a0,d1,a1,p0,p1; + STBIR_NO_UNROLL(decode); + stbir__simdf_load( d0, decode-8 ); + stbir__simdf_load( d1, decode-8+4 ); + stbir__simdf_0123to1133( p0, d0 ); + stbir__simdf_0123to1133( p1, d1 ); + stbir__simdf_0123to0022( a0, d0 ); + stbir__simdf_0123to0022( a1, d1 ); + stbir__simdf_mult( p0, p0, a0 ); + stbir__simdf_mult( p1, p1, a1 ); + + stbir__simdf_store2( out, d0 ); + stbir__simdf_store( out+2, p0 ); + stbir__simdf_store2h( out+3, d0 ); + + stbir__simdf_store2( out+6, d1 ); + stbir__simdf_store( out+8, p1 ); + stbir__simdf_store2h( out+9, d1 ); + #endif + decode += 8; + out += 12; + } while ( decode <= end_decode ); + } + decode -= 8; + #endif + + while( decode < end_decode ) + { + float x = decode[0], y = decode[1]; + STBIR_SIMD_NO_UNROLL(decode); + out[0] = x; + out[1] = y; + out[2] = x * y; + out += 3; + decode += 2; + } +} + +static void stbir__fancy_alpha_unweight_4ch( float * encode_buffer, int width_times_channels ) +{ + float STBIR_SIMD_STREAMOUT_PTR(*) encode = encode_buffer; + float STBIR_SIMD_STREAMOUT_PTR(*) input = encode_buffer; + float const * end_output = encode_buffer + width_times_channels; + + // fancy RGBA is stored internally as R G B A Rpm Gpm Bpm + + do { + float alpha = input[3]; +#ifdef STBIR_SIMD + stbir__simdf i,ia; + STBIR_SIMD_NO_UNROLL(encode); + if ( alpha < stbir__small_float ) + { + stbir__simdf_load( i, input ); + stbir__simdf_store( encode, i ); + } + else + { + stbir__simdf_load1frep4( ia, 1.0f / alpha ); + stbir__simdf_load( i, input+4 ); + stbir__simdf_mult( i, i, ia ); + stbir__simdf_store( encode, i ); + encode[3] = alpha; + } +#else + if ( alpha < stbir__small_float ) + { + encode[0] = input[0]; + encode[1] = input[1]; + encode[2] = input[2]; + } + else + { + float ialpha = 1.0f / alpha; + encode[0] = input[4] * ialpha; + encode[1] = input[5] * ialpha; + encode[2] = input[6] * ialpha; + } + encode[3] = alpha; +#endif + + input += 7; + encode += 4; + } while ( encode < end_output ); +} + +// format: [X A Xpm][X A Xpm] etc +static void stbir__fancy_alpha_unweight_2ch( float * encode_buffer, int width_times_channels ) +{ + float STBIR_SIMD_STREAMOUT_PTR(*) encode = encode_buffer; + float STBIR_SIMD_STREAMOUT_PTR(*) input = encode_buffer; + float const * end_output = encode_buffer + width_times_channels; + + do { + float alpha = input[1]; + encode[0] = input[0]; + if ( alpha >= stbir__small_float ) + encode[0] = input[2] / alpha; + encode[1] = alpha; + + input += 3; + encode += 2; + } while ( encode < end_output ); +} + +static void stbir__simple_alpha_weight_4ch( float * decode_buffer, int width_times_channels ) +{ + float STBIR_STREAMOUT_PTR(*) decode = decode_buffer; + float const * end_decode = decode_buffer + width_times_channels; + + #ifdef STBIR_SIMD + { + decode += 2 * stbir__simdfX_float_count; + while ( decode <= end_decode ) + { + stbir__simdfX d0,a0,d1,a1; + STBIR_NO_UNROLL(decode); + stbir__simdfX_load( d0, decode-2*stbir__simdfX_float_count ); + stbir__simdfX_load( d1, decode-2*stbir__simdfX_float_count+stbir__simdfX_float_count ); + stbir__simdfX_aaa1( a0, d0, STBIR_onesX ); + stbir__simdfX_aaa1( a1, d1, STBIR_onesX ); + stbir__simdfX_mult( d0, d0, a0 ); + stbir__simdfX_mult( d1, d1, a1 ); + stbir__simdfX_store ( decode-2*stbir__simdfX_float_count, d0 ); + stbir__simdfX_store ( decode-2*stbir__simdfX_float_count+stbir__simdfX_float_count, d1 ); + decode += 2 * stbir__simdfX_float_count; + } + decode -= 2 * stbir__simdfX_float_count; + + // few last pixels remnants + #ifdef STBIR_SIMD8 + while ( decode < end_decode ) + #else + if ( decode < end_decode ) + #endif + { + stbir__simdf d,a; + stbir__simdf_load( d, decode ); + stbir__simdf_aaa1( a, d, STBIR__CONSTF(STBIR_ones) ); + stbir__simdf_mult( d, d, a ); + stbir__simdf_store ( decode, d ); + decode += 4; + } + } + + #else + + while( decode < end_decode ) + { + float alpha = decode[3]; + decode[0] *= alpha; + decode[1] *= alpha; + decode[2] *= alpha; + decode += 4; + } + + #endif +} + +static void stbir__simple_alpha_weight_2ch( float * decode_buffer, int width_times_channels ) +{ + float STBIR_STREAMOUT_PTR(*) decode = decode_buffer; + float const * end_decode = decode_buffer + width_times_channels; + + #ifdef STBIR_SIMD + decode += 2 * stbir__simdfX_float_count; + while ( decode <= end_decode ) + { + stbir__simdfX d0,a0,d1,a1; + STBIR_NO_UNROLL(decode); + stbir__simdfX_load( d0, decode-2*stbir__simdfX_float_count ); + stbir__simdfX_load( d1, decode-2*stbir__simdfX_float_count+stbir__simdfX_float_count ); + stbir__simdfX_a1a1( a0, d0, STBIR_onesX ); + stbir__simdfX_a1a1( a1, d1, STBIR_onesX ); + stbir__simdfX_mult( d0, d0, a0 ); + stbir__simdfX_mult( d1, d1, a1 ); + stbir__simdfX_store ( decode-2*stbir__simdfX_float_count, d0 ); + stbir__simdfX_store ( decode-2*stbir__simdfX_float_count+stbir__simdfX_float_count, d1 ); + decode += 2 * stbir__simdfX_float_count; + } + decode -= 2 * stbir__simdfX_float_count; + #endif + + while( decode < end_decode ) + { + float alpha = decode[1]; + STBIR_SIMD_NO_UNROLL(decode); + decode[0] *= alpha; + decode += 2; + } +} + +static void stbir__simple_alpha_unweight_4ch( float * encode_buffer, int width_times_channels ) +{ + float STBIR_SIMD_STREAMOUT_PTR(*) encode = encode_buffer; + float const * end_output = encode_buffer + width_times_channels; + + do { + float alpha = encode[3]; + +#ifdef STBIR_SIMD + stbir__simdf i,ia; + STBIR_SIMD_NO_UNROLL(encode); + if ( alpha >= stbir__small_float ) + { + stbir__simdf_load1frep4( ia, 1.0f / alpha ); + stbir__simdf_load( i, encode ); + stbir__simdf_mult( i, i, ia ); + stbir__simdf_store( encode, i ); + encode[3] = alpha; + } +#else + if ( alpha >= stbir__small_float ) + { + float ialpha = 1.0f / alpha; + encode[0] *= ialpha; + encode[1] *= ialpha; + encode[2] *= ialpha; + } +#endif + encode += 4; + } while ( encode < end_output ); +} + +static void stbir__simple_alpha_unweight_2ch( float * encode_buffer, int width_times_channels ) +{ + float STBIR_SIMD_STREAMOUT_PTR(*) encode = encode_buffer; + float const * end_output = encode_buffer + width_times_channels; + + do { + float alpha = encode[1]; + if ( alpha >= stbir__small_float ) + encode[0] /= alpha; + encode += 2; + } while ( encode < end_output ); +} + + +// only used in RGB->BGR or BGR->RGB +static void stbir__simple_flip_3ch( float * decode_buffer, int width_times_channels ) +{ + float STBIR_STREAMOUT_PTR(*) decode = decode_buffer; + float const * end_decode = decode_buffer + width_times_channels; + + decode += 12; + while( decode <= end_decode ) + { + float t0,t1,t2,t3; + STBIR_NO_UNROLL(decode); + t0 = decode[0]; t1 = decode[3]; t2 = decode[6]; t3 = decode[9]; + decode[0] = decode[2]; decode[3] = decode[5]; decode[6] = decode[8]; decode[9] = decode[11]; + decode[2] = t0; decode[5] = t1; decode[8] = t2; decode[11] = t3; + decode += 12; + } + decode -= 12; + + while( decode < end_decode ) + { + float t = decode[0]; + STBIR_NO_UNROLL(decode); + decode[0] = decode[2]; + decode[2] = t; + decode += 3; + } +} + + + +static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float * output_buffer STBIR_ONLY_PROFILE_GET_SPLIT_INFO ) +{ + int channels = stbir_info->channels; + int effective_channels = stbir_info->effective_channels; + int input_sample_in_bytes = stbir__type_size[stbir_info->input_type] * channels; + stbir_edge edge_horizontal = stbir_info->horizontal.edge; + stbir_edge edge_vertical = stbir_info->vertical.edge; + int row = stbir__edge_wrap(edge_vertical, n, stbir_info->vertical.scale_info.input_full_size); + const void* input_plane_data = ( (char *) stbir_info->input_data ) + (ptrdiff_t)row * (ptrdiff_t) stbir_info->input_stride_bytes; + stbir__span const * spans = stbir_info->scanline_extents.spans; + float* full_decode_buffer = output_buffer - stbir_info->scanline_extents.conservative.n0 * effective_channels; + + // if we are on edge_zero, and we get in here with an out of bounds n, then the calculate filters has failed + STBIR_ASSERT( !(edge_vertical == STBIR_EDGE_ZERO && (n < 0 || n >= stbir_info->vertical.scale_info.input_full_size)) ); + + do + { + float * decode_buffer; + void const * input_data; + float * end_decode; + int width_times_channels; + int width; + + if ( spans->n1 < spans->n0 ) + break; + + width = spans->n1 + 1 - spans->n0; + decode_buffer = full_decode_buffer + spans->n0 * effective_channels; + end_decode = full_decode_buffer + ( spans->n1 + 1 ) * effective_channels; + width_times_channels = width * channels; + + // read directly out of input plane by default + input_data = ( (char*)input_plane_data ) + spans->pixel_offset_for_input * input_sample_in_bytes; + + // if we have an input callback, call it to get the input data + if ( stbir_info->in_pixels_cb ) + { + // call the callback with a temp buffer (that they can choose to use or not). the temp is just right aligned memory in the decode_buffer itself + input_data = stbir_info->in_pixels_cb( ( (char*) end_decode ) - ( width * input_sample_in_bytes ), input_plane_data, width, spans->pixel_offset_for_input, row, stbir_info->user_data ); + } + + STBIR_PROFILE_START( decode ); + // convert the pixels info the float decode_buffer, (we index from end_decode, so that when channelsdecode_pixels( (float*)end_decode - width_times_channels, width_times_channels, input_data ); + STBIR_PROFILE_END( decode ); + + if (stbir_info->alpha_weight) + { + STBIR_PROFILE_START( alpha ); + stbir_info->alpha_weight( decode_buffer, width_times_channels ); + STBIR_PROFILE_END( alpha ); + } + + ++spans; + } while ( spans <= ( &stbir_info->scanline_extents.spans[1] ) ); + + // handle the edge_wrap filter (all other types are handled back out at the calculate_filter stage) + // basically the idea here is that if we have the whole scanline in memory, we don't redecode the + // wrapped edge pixels, and instead just memcpy them from the scanline into the edge positions + if ( ( edge_horizontal == STBIR_EDGE_WRAP ) && ( stbir_info->scanline_extents.edge_sizes[0] | stbir_info->scanline_extents.edge_sizes[1] ) ) + { + // this code only runs if we're in edge_wrap, and we're doing the entire scanline + int e, start_x[2]; + int input_full_size = stbir_info->horizontal.scale_info.input_full_size; + + start_x[0] = -stbir_info->scanline_extents.edge_sizes[0]; // left edge start x + start_x[1] = input_full_size; // right edge + + for( e = 0; e < 2 ; e++ ) + { + // do each margin + int margin = stbir_info->scanline_extents.edge_sizes[e]; + if ( margin ) + { + int x = start_x[e]; + float * marg = full_decode_buffer + x * effective_channels; + float const * src = full_decode_buffer + stbir__edge_wrap(edge_horizontal, x, input_full_size) * effective_channels; + STBIR_MEMCPY( marg, src, margin * effective_channels * sizeof(float) ); + } + } + } +} + + +//================= +// Do 1 channel horizontal routines + +#ifdef STBIR_SIMD + +#define stbir__1_coeff_only() \ + stbir__simdf tot,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1( c, hc ); \ + stbir__simdf_mult1_mem( tot, c, decode ); + +#define stbir__2_coeff_only() \ + stbir__simdf tot,c,d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2z( c, hc ); \ + stbir__simdf_load2( d, decode ); \ + stbir__simdf_mult( tot, c, d ); \ + stbir__simdf_0123to1230( c, tot ); \ + stbir__simdf_add1( tot, tot, c ); + +#define stbir__3_coeff_only() \ + stbir__simdf tot,c,t; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( c, hc ); \ + stbir__simdf_mult_mem( tot, c, decode ); \ + stbir__simdf_0123to1230( c, tot ); \ + stbir__simdf_0123to2301( t, tot ); \ + stbir__simdf_add1( tot, tot, c ); \ + stbir__simdf_add1( tot, tot, t ); + +#define stbir__store_output_tiny() \ + stbir__simdf_store1( output, tot ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 1; + +#define stbir__4_coeff_start() \ + stbir__simdf tot,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( c, hc ); \ + stbir__simdf_mult_mem( tot, c, decode ); \ + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( c, hc + (ofs) ); \ + stbir__simdf_madd_mem( tot, tot, c, decode+(ofs) ); + +#define stbir__1_coeff_remnant( ofs ) \ + { stbir__simdf d; \ + stbir__simdf_load1z( c, hc + (ofs) ); \ + stbir__simdf_load1( d, decode + (ofs) ); \ + stbir__simdf_madd( tot, tot, d, c ); } + +#define stbir__2_coeff_remnant( ofs ) \ + { stbir__simdf d; \ + stbir__simdf_load2z( c, hc+(ofs) ); \ + stbir__simdf_load2( d, decode+(ofs) ); \ + stbir__simdf_madd( tot, tot, d, c ); } + +#define stbir__3_coeff_setup() \ + stbir__simdf mask; \ + stbir__simdf_load( mask, STBIR_mask + 3 ); + +#define stbir__3_coeff_remnant( ofs ) \ + stbir__simdf_load( c, hc+(ofs) ); \ + stbir__simdf_and( c, c, mask ); \ + stbir__simdf_madd_mem( tot, tot, c, decode+(ofs) ); + +#define stbir__store_output() \ + stbir__simdf_0123to2301( c, tot ); \ + stbir__simdf_add( tot, tot, c ); \ + stbir__simdf_0123to1230( c, tot ); \ + stbir__simdf_add1( tot, tot, c ); \ + stbir__simdf_store1( output, tot ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 1; + +#else + +#define stbir__1_coeff_only() \ + float tot; \ + tot = decode[0]*hc[0]; + +#define stbir__2_coeff_only() \ + float tot; \ + tot = decode[0] * hc[0]; \ + tot += decode[1] * hc[1]; + +#define stbir__3_coeff_only() \ + float tot; \ + tot = decode[0] * hc[0]; \ + tot += decode[1] * hc[1]; \ + tot += decode[2] * hc[2]; + +#define stbir__store_output_tiny() \ + output[0] = tot; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 1; + +#define stbir__4_coeff_start() \ + float tot0,tot1,tot2,tot3; \ + tot0 = decode[0] * hc[0]; \ + tot1 = decode[1] * hc[1]; \ + tot2 = decode[2] * hc[2]; \ + tot3 = decode[3] * hc[3]; + +#define stbir__4_coeff_continue_from_4( ofs ) \ + tot0 += decode[0+(ofs)] * hc[0+(ofs)]; \ + tot1 += decode[1+(ofs)] * hc[1+(ofs)]; \ + tot2 += decode[2+(ofs)] * hc[2+(ofs)]; \ + tot3 += decode[3+(ofs)] * hc[3+(ofs)]; + +#define stbir__1_coeff_remnant( ofs ) \ + tot0 += decode[0+(ofs)] * hc[0+(ofs)]; + +#define stbir__2_coeff_remnant( ofs ) \ + tot0 += decode[0+(ofs)] * hc[0+(ofs)]; \ + tot1 += decode[1+(ofs)] * hc[1+(ofs)]; \ + +#define stbir__3_coeff_remnant( ofs ) \ + tot0 += decode[0+(ofs)] * hc[0+(ofs)]; \ + tot1 += decode[1+(ofs)] * hc[1+(ofs)]; \ + tot2 += decode[2+(ofs)] * hc[2+(ofs)]; + +#define stbir__store_output() \ + output[0] = (tot0+tot2)+(tot1+tot3); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 1; + +#endif + +#define STBIR__horizontal_channels 1 +#define STB_IMAGE_RESIZE_DO_HORIZONTALS +#include STBIR__HEADER_FILENAME + + +//================= +// Do 2 channel horizontal routines + +#ifdef STBIR_SIMD + +#define stbir__1_coeff_only() \ + stbir__simdf tot,c,d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1z( c, hc ); \ + stbir__simdf_0123to0011( c, c ); \ + stbir__simdf_load2( d, decode ); \ + stbir__simdf_mult( tot, d, c ); + +#define stbir__2_coeff_only() \ + stbir__simdf tot,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2( c, hc ); \ + stbir__simdf_0123to0011( c, c ); \ + stbir__simdf_mult_mem( tot, c, decode ); + +#define stbir__3_coeff_only() \ + stbir__simdf tot,c,cs,d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0011( c, cs ); \ + stbir__simdf_mult_mem( tot, c, decode ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_load2z( d, decode+4 ); \ + stbir__simdf_madd( tot, tot, d, c ); + +#define stbir__store_output_tiny() \ + stbir__simdf_0123to2301( c, tot ); \ + stbir__simdf_add( tot, tot, c ); \ + stbir__simdf_store2( output, tot ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 2; + +#ifdef STBIR_SIMD8 + +#define stbir__4_coeff_start() \ + stbir__simdf8 tot0,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc ); \ + stbir__simdf8_0123to00112233( c, cs ); \ + stbir__simdf8_mult_mem( tot0, c, decode ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00112233( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*2 ); + +#define stbir__1_coeff_remnant( ofs ) \ + { stbir__simdf t; \ + stbir__simdf_load1z( t, hc + (ofs) ); \ + stbir__simdf_0123to0011( t, t ); \ + stbir__simdf_mult_mem( t, t, decode+(ofs)*2 ); \ + stbir__simdf8_add4( tot0, tot0, t ); } + +#define stbir__2_coeff_remnant( ofs ) \ + { stbir__simdf t; \ + stbir__simdf_load2( t, hc + (ofs) ); \ + stbir__simdf_0123to0011( t, t ); \ + stbir__simdf_mult_mem( t, t, decode+(ofs)*2 ); \ + stbir__simdf8_add4( tot0, tot0, t ); } + +#define stbir__3_coeff_remnant( ofs ) \ + { stbir__simdf8 d; \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00112233( c, cs ); \ + stbir__simdf8_load6z( d, decode+(ofs)*2 ); \ + stbir__simdf8_madd( tot0, tot0, c, d ); } + +#define stbir__store_output() \ + { stbir__simdf t,d; \ + stbir__simdf8_add4halves( t, stbir__if_simdf8_cast_to_simdf4(tot0), tot0 ); \ + stbir__simdf_0123to2301( d, t ); \ + stbir__simdf_add( t, t, d ); \ + stbir__simdf_store2( output, t ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 2; } + +#else + +#define stbir__4_coeff_start() \ + stbir__simdf tot0,tot1,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0011( c, cs ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_0123to2233( c, cs ); \ + stbir__simdf_mult_mem( tot1, c, decode+4 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0011( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*2 ); \ + stbir__simdf_0123to2233( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*2+4 ); + +#define stbir__1_coeff_remnant( ofs ) \ + { stbir__simdf d; \ + stbir__simdf_load1z( cs, hc + (ofs) ); \ + stbir__simdf_0123to0011( c, cs ); \ + stbir__simdf_load2( d, decode + (ofs) * 2 ); \ + stbir__simdf_madd( tot0, tot0, d, c ); } + +#define stbir__2_coeff_remnant( ofs ) \ + stbir__simdf_load2( cs, hc + (ofs) ); \ + stbir__simdf_0123to0011( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*2 ); + +#define stbir__3_coeff_remnant( ofs ) \ + { stbir__simdf d; \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0011( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*2 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_load2z( d, decode + (ofs) * 2 + 4 ); \ + stbir__simdf_madd( tot1, tot1, d, c ); } + +#define stbir__store_output() \ + stbir__simdf_add( tot0, tot0, tot1 ); \ + stbir__simdf_0123to2301( c, tot0 ); \ + stbir__simdf_add( tot0, tot0, c ); \ + stbir__simdf_store2( output, tot0 ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 2; + +#endif + +#else + +#define stbir__1_coeff_only() \ + float tota,totb,c; \ + c = hc[0]; \ + tota = decode[0]*c; \ + totb = decode[1]*c; + +#define stbir__2_coeff_only() \ + float tota,totb,c; \ + c = hc[0]; \ + tota = decode[0]*c; \ + totb = decode[1]*c; \ + c = hc[1]; \ + tota += decode[2]*c; \ + totb += decode[3]*c; + +// this weird order of add matches the simd +#define stbir__3_coeff_only() \ + float tota,totb,c; \ + c = hc[0]; \ + tota = decode[0]*c; \ + totb = decode[1]*c; \ + c = hc[2]; \ + tota += decode[4]*c; \ + totb += decode[5]*c; \ + c = hc[1]; \ + tota += decode[2]*c; \ + totb += decode[3]*c; + +#define stbir__store_output_tiny() \ + output[0] = tota; \ + output[1] = totb; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 2; + +#define stbir__4_coeff_start() \ + float tota0,tota1,tota2,tota3,totb0,totb1,totb2,totb3,c; \ + c = hc[0]; \ + tota0 = decode[0]*c; \ + totb0 = decode[1]*c; \ + c = hc[1]; \ + tota1 = decode[2]*c; \ + totb1 = decode[3]*c; \ + c = hc[2]; \ + tota2 = decode[4]*c; \ + totb2 = decode[5]*c; \ + c = hc[3]; \ + tota3 = decode[6]*c; \ + totb3 = decode[7]*c; + +#define stbir__4_coeff_continue_from_4( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*2]*c; \ + totb0 += decode[1+(ofs)*2]*c; \ + c = hc[1+(ofs)]; \ + tota1 += decode[2+(ofs)*2]*c; \ + totb1 += decode[3+(ofs)*2]*c; \ + c = hc[2+(ofs)]; \ + tota2 += decode[4+(ofs)*2]*c; \ + totb2 += decode[5+(ofs)*2]*c; \ + c = hc[3+(ofs)]; \ + tota3 += decode[6+(ofs)*2]*c; \ + totb3 += decode[7+(ofs)*2]*c; + +#define stbir__1_coeff_remnant( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*2] * c; \ + totb0 += decode[1+(ofs)*2] * c; + +#define stbir__2_coeff_remnant( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*2] * c; \ + totb0 += decode[1+(ofs)*2] * c; \ + c = hc[1+(ofs)]; \ + tota1 += decode[2+(ofs)*2] * c; \ + totb1 += decode[3+(ofs)*2] * c; + +#define stbir__3_coeff_remnant( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*2] * c; \ + totb0 += decode[1+(ofs)*2] * c; \ + c = hc[1+(ofs)]; \ + tota1 += decode[2+(ofs)*2] * c; \ + totb1 += decode[3+(ofs)*2] * c; \ + c = hc[2+(ofs)]; \ + tota2 += decode[4+(ofs)*2] * c; \ + totb2 += decode[5+(ofs)*2] * c; + +#define stbir__store_output() \ + output[0] = (tota0+tota2)+(tota1+tota3); \ + output[1] = (totb0+totb2)+(totb1+totb3); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 2; + +#endif + +#define STBIR__horizontal_channels 2 +#define STB_IMAGE_RESIZE_DO_HORIZONTALS +#include STBIR__HEADER_FILENAME + + +//================= +// Do 3 channel horizontal routines + +#ifdef STBIR_SIMD + +#define stbir__1_coeff_only() \ + stbir__simdf tot,c,d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1z( c, hc ); \ + stbir__simdf_0123to0001( c, c ); \ + stbir__simdf_load( d, decode ); \ + stbir__simdf_mult( tot, d, c ); + +#define stbir__2_coeff_only() \ + stbir__simdf tot,c,cs,d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_load( d, decode ); \ + stbir__simdf_mult( tot, d, c ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_load( d, decode+3 ); \ + stbir__simdf_madd( tot, tot, d, c ); + +#define stbir__3_coeff_only() \ + stbir__simdf tot,c,d,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_load( d, decode ); \ + stbir__simdf_mult( tot, d, c ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_load( d, decode+3 ); \ + stbir__simdf_madd( tot, tot, d, c ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_load( d, decode+6 ); \ + stbir__simdf_madd( tot, tot, d, c ); + +#define stbir__store_output_tiny() \ + stbir__simdf_store2( output, tot ); \ + stbir__simdf_0123to2301( tot, tot ); \ + stbir__simdf_store1( output+2, tot ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 3; + +#ifdef STBIR_SIMD8 + +// we're loading from the XXXYYY decode by -1 to get the XXXYYY into different halves of the AVX reg fyi +#define stbir__4_coeff_start() \ + stbir__simdf8 tot0,tot1,c,cs; stbir__simdf t; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc ); \ + stbir__simdf8_0123to00001111( c, cs ); \ + stbir__simdf8_mult_mem( tot0, c, decode - 1 ); \ + stbir__simdf8_0123to22223333( c, cs ); \ + stbir__simdf8_mult_mem( tot1, c, decode+6 - 1 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00001111( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*3 - 1 ); \ + stbir__simdf8_0123to22223333( c, cs ); \ + stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*3 + 6 - 1 ); + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1rep4( t, hc + (ofs) ); \ + stbir__simdf8_madd_mem4( tot0, tot0, t, decode+(ofs)*3 - 1 ); + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) - 2 ); \ + stbir__simdf8_0123to22223333( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*3 - 1 ); + + #define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00001111( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*3 - 1 ); \ + stbir__simdf8_0123to2222( t, cs ); \ + stbir__simdf8_madd_mem4( tot1, tot1, t, decode+(ofs)*3 + 6 - 1 ); + +#define stbir__store_output() \ + stbir__simdf8_add( tot0, tot0, tot1 ); \ + stbir__simdf_0123to1230( t, stbir__if_simdf8_cast_to_simdf4( tot0 ) ); \ + stbir__simdf8_add4halves( t, t, tot0 ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 3; \ + if ( output < output_end ) \ + { \ + stbir__simdf_store( output-3, t ); \ + continue; \ + } \ + { stbir__simdf tt; stbir__simdf_0123to2301( tt, t ); \ + stbir__simdf_store2( output-3, t ); \ + stbir__simdf_store1( output+2-3, tt ); } \ + break; + + +#else + +#define stbir__4_coeff_start() \ + stbir__simdf tot0,tot1,tot2,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0001( c, cs ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_0123to1122( c, cs ); \ + stbir__simdf_mult_mem( tot1, c, decode+4 ); \ + stbir__simdf_0123to2333( c, cs ); \ + stbir__simdf_mult_mem( tot2, c, decode+8 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0001( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*3 ); \ + stbir__simdf_0123to1122( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*3+4 ); \ + stbir__simdf_0123to2333( c, cs ); \ + stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*3+8 ); + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1z( c, hc + (ofs) ); \ + stbir__simdf_0123to0001( c, c ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*3 ); + +#define stbir__2_coeff_remnant( ofs ) \ + { stbir__simdf d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2z( cs, hc + (ofs) ); \ + stbir__simdf_0123to0001( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*3 ); \ + stbir__simdf_0123to1122( c, cs ); \ + stbir__simdf_load2z( d, decode+(ofs)*3+4 ); \ + stbir__simdf_madd( tot1, tot1, c, d ); } + +#define stbir__3_coeff_remnant( ofs ) \ + { stbir__simdf d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0001( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*3 ); \ + stbir__simdf_0123to1122( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*3+4 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_load1z( d, decode+(ofs)*3+8 ); \ + stbir__simdf_madd( tot2, tot2, c, d ); } + +#define stbir__store_output() \ + stbir__simdf_0123ABCDto3ABx( c, tot0, tot1 ); \ + stbir__simdf_0123ABCDto23Ax( cs, tot1, tot2 ); \ + stbir__simdf_0123to1230( tot2, tot2 ); \ + stbir__simdf_add( tot0, tot0, cs ); \ + stbir__simdf_add( c, c, tot2 ); \ + stbir__simdf_add( tot0, tot0, c ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 3; \ + if ( output < output_end ) \ + { \ + stbir__simdf_store( output-3, tot0 ); \ + continue; \ + } \ + stbir__simdf_0123to2301( tot1, tot0 ); \ + stbir__simdf_store2( output-3, tot0 ); \ + stbir__simdf_store1( output+2-3, tot1 ); \ + break; + +#endif + +#else + +#define stbir__1_coeff_only() \ + float tot0, tot1, tot2, c; \ + c = hc[0]; \ + tot0 = decode[0]*c; \ + tot1 = decode[1]*c; \ + tot2 = decode[2]*c; + +#define stbir__2_coeff_only() \ + float tot0, tot1, tot2, c; \ + c = hc[0]; \ + tot0 = decode[0]*c; \ + tot1 = decode[1]*c; \ + tot2 = decode[2]*c; \ + c = hc[1]; \ + tot0 += decode[3]*c; \ + tot1 += decode[4]*c; \ + tot2 += decode[5]*c; + +#define stbir__3_coeff_only() \ + float tot0, tot1, tot2, c; \ + c = hc[0]; \ + tot0 = decode[0]*c; \ + tot1 = decode[1]*c; \ + tot2 = decode[2]*c; \ + c = hc[1]; \ + tot0 += decode[3]*c; \ + tot1 += decode[4]*c; \ + tot2 += decode[5]*c; \ + c = hc[2]; \ + tot0 += decode[6]*c; \ + tot1 += decode[7]*c; \ + tot2 += decode[8]*c; + +#define stbir__store_output_tiny() \ + output[0] = tot0; \ + output[1] = tot1; \ + output[2] = tot2; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 3; + +#define stbir__4_coeff_start() \ + float tota0,tota1,tota2,totb0,totb1,totb2,totc0,totc1,totc2,totd0,totd1,totd2,c; \ + c = hc[0]; \ + tota0 = decode[0]*c; \ + tota1 = decode[1]*c; \ + tota2 = decode[2]*c; \ + c = hc[1]; \ + totb0 = decode[3]*c; \ + totb1 = decode[4]*c; \ + totb2 = decode[5]*c; \ + c = hc[2]; \ + totc0 = decode[6]*c; \ + totc1 = decode[7]*c; \ + totc2 = decode[8]*c; \ + c = hc[3]; \ + totd0 = decode[9]*c; \ + totd1 = decode[10]*c; \ + totd2 = decode[11]*c; + +#define stbir__4_coeff_continue_from_4( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*3]*c; \ + tota1 += decode[1+(ofs)*3]*c; \ + tota2 += decode[2+(ofs)*3]*c; \ + c = hc[1+(ofs)]; \ + totb0 += decode[3+(ofs)*3]*c; \ + totb1 += decode[4+(ofs)*3]*c; \ + totb2 += decode[5+(ofs)*3]*c; \ + c = hc[2+(ofs)]; \ + totc0 += decode[6+(ofs)*3]*c; \ + totc1 += decode[7+(ofs)*3]*c; \ + totc2 += decode[8+(ofs)*3]*c; \ + c = hc[3+(ofs)]; \ + totd0 += decode[9+(ofs)*3]*c; \ + totd1 += decode[10+(ofs)*3]*c; \ + totd2 += decode[11+(ofs)*3]*c; + +#define stbir__1_coeff_remnant( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*3]*c; \ + tota1 += decode[1+(ofs)*3]*c; \ + tota2 += decode[2+(ofs)*3]*c; + +#define stbir__2_coeff_remnant( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*3]*c; \ + tota1 += decode[1+(ofs)*3]*c; \ + tota2 += decode[2+(ofs)*3]*c; \ + c = hc[1+(ofs)]; \ + totb0 += decode[3+(ofs)*3]*c; \ + totb1 += decode[4+(ofs)*3]*c; \ + totb2 += decode[5+(ofs)*3]*c; \ + +#define stbir__3_coeff_remnant( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*3]*c; \ + tota1 += decode[1+(ofs)*3]*c; \ + tota2 += decode[2+(ofs)*3]*c; \ + c = hc[1+(ofs)]; \ + totb0 += decode[3+(ofs)*3]*c; \ + totb1 += decode[4+(ofs)*3]*c; \ + totb2 += decode[5+(ofs)*3]*c; \ + c = hc[2+(ofs)]; \ + totc0 += decode[6+(ofs)*3]*c; \ + totc1 += decode[7+(ofs)*3]*c; \ + totc2 += decode[8+(ofs)*3]*c; + +#define stbir__store_output() \ + output[0] = (tota0+totc0)+(totb0+totd0); \ + output[1] = (tota1+totc1)+(totb1+totd1); \ + output[2] = (tota2+totc2)+(totb2+totd2); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 3; + +#endif + +#define STBIR__horizontal_channels 3 +#define STB_IMAGE_RESIZE_DO_HORIZONTALS +#include STBIR__HEADER_FILENAME + +//================= +// Do 4 channel horizontal routines + +#ifdef STBIR_SIMD + +#define stbir__1_coeff_only() \ + stbir__simdf tot,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1( c, hc ); \ + stbir__simdf_0123to0000( c, c ); \ + stbir__simdf_mult_mem( tot, c, decode ); + +#define stbir__2_coeff_only() \ + stbir__simdf tot,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_mult_mem( tot, c, decode ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot, tot, c, decode+4 ); + +#define stbir__3_coeff_only() \ + stbir__simdf tot,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_mult_mem( tot, c, decode ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot, tot, c, decode+4 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot, tot, c, decode+8 ); + +#define stbir__store_output_tiny() \ + stbir__simdf_store( output, tot ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 4; + +#ifdef STBIR_SIMD8 + +#define stbir__4_coeff_start() \ + stbir__simdf8 tot0,c,cs; stbir__simdf t; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc ); \ + stbir__simdf8_0123to00001111( c, cs ); \ + stbir__simdf8_mult_mem( tot0, c, decode ); \ + stbir__simdf8_0123to22223333( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+8 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00001111( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); \ + stbir__simdf8_0123to22223333( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*4+8 ); + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1rep4( t, hc + (ofs) ); \ + stbir__simdf8_madd_mem4( tot0, tot0, t, decode+(ofs)*4 ); + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) - 2 ); \ + stbir__simdf8_0123to22223333( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); + + #define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00001111( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); \ + stbir__simdf8_0123to2222( t, cs ); \ + stbir__simdf8_madd_mem4( tot0, tot0, t, decode+(ofs)*4+8 ); + +#define stbir__store_output() \ + stbir__simdf8_add4halves( t, stbir__if_simdf8_cast_to_simdf4(tot0), tot0 ); \ + stbir__simdf_store( output, t ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 4; + +#else + +#define stbir__4_coeff_start() \ + stbir__simdf tot0,tot1,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_mult_mem( tot1, c, decode+4 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+8 ); \ + stbir__simdf_0123to3333( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+12 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*4+4 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4+8 ); \ + stbir__simdf_0123to3333( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*4+12 ); + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1( c, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, c ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2( cs, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*4+4 ); + +#define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*4+4 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4+8 ); + +#define stbir__store_output() \ + stbir__simdf_add( tot0, tot0, tot1 ); \ + stbir__simdf_store( output, tot0 ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 4; + +#endif + +#else + +#define stbir__1_coeff_only() \ + float p0,p1,p2,p3,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0]; \ + p0 = decode[0] * c; \ + p1 = decode[1] * c; \ + p2 = decode[2] * c; \ + p3 = decode[3] * c; + +#define stbir__2_coeff_only() \ + float p0,p1,p2,p3,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0]; \ + p0 = decode[0] * c; \ + p1 = decode[1] * c; \ + p2 = decode[2] * c; \ + p3 = decode[3] * c; \ + c = hc[1]; \ + p0 += decode[4] * c; \ + p1 += decode[5] * c; \ + p2 += decode[6] * c; \ + p3 += decode[7] * c; + +#define stbir__3_coeff_only() \ + float p0,p1,p2,p3,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0]; \ + p0 = decode[0] * c; \ + p1 = decode[1] * c; \ + p2 = decode[2] * c; \ + p3 = decode[3] * c; \ + c = hc[1]; \ + p0 += decode[4] * c; \ + p1 += decode[5] * c; \ + p2 += decode[6] * c; \ + p3 += decode[7] * c; \ + c = hc[2]; \ + p0 += decode[8] * c; \ + p1 += decode[9] * c; \ + p2 += decode[10] * c; \ + p3 += decode[11] * c; + +#define stbir__store_output_tiny() \ + output[0] = p0; \ + output[1] = p1; \ + output[2] = p2; \ + output[3] = p3; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 4; + +#define stbir__4_coeff_start() \ + float x0,x1,x2,x3,y0,y1,y2,y3,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0]; \ + x0 = decode[0] * c; \ + x1 = decode[1] * c; \ + x2 = decode[2] * c; \ + x3 = decode[3] * c; \ + c = hc[1]; \ + y0 = decode[4] * c; \ + y1 = decode[5] * c; \ + y2 = decode[6] * c; \ + y3 = decode[7] * c; \ + c = hc[2]; \ + x0 += decode[8] * c; \ + x1 += decode[9] * c; \ + x2 += decode[10] * c; \ + x3 += decode[11] * c; \ + c = hc[3]; \ + y0 += decode[12] * c; \ + y1 += decode[13] * c; \ + y2 += decode[14] * c; \ + y3 += decode[15] * c; + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*4] * c; \ + x1 += decode[1+(ofs)*4] * c; \ + x2 += decode[2+(ofs)*4] * c; \ + x3 += decode[3+(ofs)*4] * c; \ + c = hc[1+(ofs)]; \ + y0 += decode[4+(ofs)*4] * c; \ + y1 += decode[5+(ofs)*4] * c; \ + y2 += decode[6+(ofs)*4] * c; \ + y3 += decode[7+(ofs)*4] * c; \ + c = hc[2+(ofs)]; \ + x0 += decode[8+(ofs)*4] * c; \ + x1 += decode[9+(ofs)*4] * c; \ + x2 += decode[10+(ofs)*4] * c; \ + x3 += decode[11+(ofs)*4] * c; \ + c = hc[3+(ofs)]; \ + y0 += decode[12+(ofs)*4] * c; \ + y1 += decode[13+(ofs)*4] * c; \ + y2 += decode[14+(ofs)*4] * c; \ + y3 += decode[15+(ofs)*4] * c; + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*4] * c; \ + x1 += decode[1+(ofs)*4] * c; \ + x2 += decode[2+(ofs)*4] * c; \ + x3 += decode[3+(ofs)*4] * c; + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*4] * c; \ + x1 += decode[1+(ofs)*4] * c; \ + x2 += decode[2+(ofs)*4] * c; \ + x3 += decode[3+(ofs)*4] * c; \ + c = hc[1+(ofs)]; \ + y0 += decode[4+(ofs)*4] * c; \ + y1 += decode[5+(ofs)*4] * c; \ + y2 += decode[6+(ofs)*4] * c; \ + y3 += decode[7+(ofs)*4] * c; + +#define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*4] * c; \ + x1 += decode[1+(ofs)*4] * c; \ + x2 += decode[2+(ofs)*4] * c; \ + x3 += decode[3+(ofs)*4] * c; \ + c = hc[1+(ofs)]; \ + y0 += decode[4+(ofs)*4] * c; \ + y1 += decode[5+(ofs)*4] * c; \ + y2 += decode[6+(ofs)*4] * c; \ + y3 += decode[7+(ofs)*4] * c; \ + c = hc[2+(ofs)]; \ + x0 += decode[8+(ofs)*4] * c; \ + x1 += decode[9+(ofs)*4] * c; \ + x2 += decode[10+(ofs)*4] * c; \ + x3 += decode[11+(ofs)*4] * c; + +#define stbir__store_output() \ + output[0] = x0 + y0; \ + output[1] = x1 + y1; \ + output[2] = x2 + y2; \ + output[3] = x3 + y3; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 4; + +#endif + +#define STBIR__horizontal_channels 4 +#define STB_IMAGE_RESIZE_DO_HORIZONTALS +#include STBIR__HEADER_FILENAME + + + +//================= +// Do 7 channel horizontal routines + +#ifdef STBIR_SIMD + +#define stbir__1_coeff_only() \ + stbir__simdf tot0,tot1,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1( c, hc ); \ + stbir__simdf_0123to0000( c, c ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_mult_mem( tot1, c, decode+3 ); + +#define stbir__2_coeff_only() \ + stbir__simdf tot0,tot1,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_mult_mem( tot1, c, decode+3 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+7 ); \ + stbir__simdf_madd_mem( tot1, tot1, c,decode+10 ); + +#define stbir__3_coeff_only() \ + stbir__simdf tot0,tot1,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_mult_mem( tot1, c, decode+3 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+7 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+10 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+14 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+17 ); + +#define stbir__store_output_tiny() \ + stbir__simdf_store( output+3, tot1 ); \ + stbir__simdf_store( output, tot0 ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 7; + +#ifdef STBIR_SIMD8 + +#define stbir__4_coeff_start() \ + stbir__simdf8 tot0,tot1,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc ); \ + stbir__simdf8_0123to00000000( c, cs ); \ + stbir__simdf8_mult_mem( tot0, c, decode ); \ + stbir__simdf8_0123to11111111( c, cs ); \ + stbir__simdf8_mult_mem( tot1, c, decode+7 ); \ + stbir__simdf8_0123to22222222( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+14 ); \ + stbir__simdf8_0123to33333333( c, cs ); \ + stbir__simdf8_madd_mem( tot1, tot1, c, decode+21 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00000000( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf8_0123to11111111( c, cs ); \ + stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*7+7 ); \ + stbir__simdf8_0123to22222222( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7+14 ); \ + stbir__simdf8_0123to33333333( c, cs ); \ + stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*7+21 ); + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load1b( c, hc + (ofs) ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load1b( c, hc + (ofs) ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf8_load1b( c, hc + (ofs)+1 ); \ + stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*7+7 ); + +#define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00000000( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf8_0123to11111111( c, cs ); \ + stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*7+7 ); \ + stbir__simdf8_0123to22222222( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7+14 ); + +#define stbir__store_output() \ + stbir__simdf8_add( tot0, tot0, tot1 ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 7; \ + if ( output < output_end ) \ + { \ + stbir__simdf8_store( output-7, tot0 ); \ + continue; \ + } \ + stbir__simdf_store( output-7+3, stbir__simdf_swiz(stbir__simdf8_gettop4(tot0),0,0,1,2) ); \ + stbir__simdf_store( output-7, stbir__if_simdf8_cast_to_simdf4(tot0) ); \ + break; + +#else + +#define stbir__4_coeff_start() \ + stbir__simdf tot0,tot1,tot2,tot3,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_mult_mem( tot1, c, decode+3 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_mult_mem( tot2, c, decode+7 ); \ + stbir__simdf_mult_mem( tot3, c, decode+10 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+14 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+17 ); \ + stbir__simdf_0123to3333( c, cs ); \ + stbir__simdf_madd_mem( tot2, tot2, c, decode+21 ); \ + stbir__simdf_madd_mem( tot3, tot3, c, decode+24 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+3 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*7+7 ); \ + stbir__simdf_madd_mem( tot3, tot3, c, decode+(ofs)*7+10 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7+14 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+17 ); \ + stbir__simdf_0123to3333( c, cs ); \ + stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*7+21 ); \ + stbir__simdf_madd_mem( tot3, tot3, c, decode+(ofs)*7+24 ); + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1( c, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, c ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+3 ); \ + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2( cs, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+3 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*7+7 ); \ + stbir__simdf_madd_mem( tot3, tot3, c, decode+(ofs)*7+10 ); + +#define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+3 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*7+7 ); \ + stbir__simdf_madd_mem( tot3, tot3, c, decode+(ofs)*7+10 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7+14 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+17 ); + +#define stbir__store_output() \ + stbir__simdf_add( tot0, tot0, tot2 ); \ + stbir__simdf_add( tot1, tot1, tot3 ); \ + stbir__simdf_store( output+3, tot1 ); \ + stbir__simdf_store( output, tot0 ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 7; + +#endif + +#else + +#define stbir__1_coeff_only() \ + float tot0, tot1, tot2, tot3, tot4, tot5, tot6, c; \ + c = hc[0]; \ + tot0 = decode[0]*c; \ + tot1 = decode[1]*c; \ + tot2 = decode[2]*c; \ + tot3 = decode[3]*c; \ + tot4 = decode[4]*c; \ + tot5 = decode[5]*c; \ + tot6 = decode[6]*c; + +#define stbir__2_coeff_only() \ + float tot0, tot1, tot2, tot3, tot4, tot5, tot6, c; \ + c = hc[0]; \ + tot0 = decode[0]*c; \ + tot1 = decode[1]*c; \ + tot2 = decode[2]*c; \ + tot3 = decode[3]*c; \ + tot4 = decode[4]*c; \ + tot5 = decode[5]*c; \ + tot6 = decode[6]*c; \ + c = hc[1]; \ + tot0 += decode[7]*c; \ + tot1 += decode[8]*c; \ + tot2 += decode[9]*c; \ + tot3 += decode[10]*c; \ + tot4 += decode[11]*c; \ + tot5 += decode[12]*c; \ + tot6 += decode[13]*c; \ + +#define stbir__3_coeff_only() \ + float tot0, tot1, tot2, tot3, tot4, tot5, tot6, c; \ + c = hc[0]; \ + tot0 = decode[0]*c; \ + tot1 = decode[1]*c; \ + tot2 = decode[2]*c; \ + tot3 = decode[3]*c; \ + tot4 = decode[4]*c; \ + tot5 = decode[5]*c; \ + tot6 = decode[6]*c; \ + c = hc[1]; \ + tot0 += decode[7]*c; \ + tot1 += decode[8]*c; \ + tot2 += decode[9]*c; \ + tot3 += decode[10]*c; \ + tot4 += decode[11]*c; \ + tot5 += decode[12]*c; \ + tot6 += decode[13]*c; \ + c = hc[2]; \ + tot0 += decode[14]*c; \ + tot1 += decode[15]*c; \ + tot2 += decode[16]*c; \ + tot3 += decode[17]*c; \ + tot4 += decode[18]*c; \ + tot5 += decode[19]*c; \ + tot6 += decode[20]*c; \ + +#define stbir__store_output_tiny() \ + output[0] = tot0; \ + output[1] = tot1; \ + output[2] = tot2; \ + output[3] = tot3; \ + output[4] = tot4; \ + output[5] = tot5; \ + output[6] = tot6; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 7; + +#define stbir__4_coeff_start() \ + float x0,x1,x2,x3,x4,x5,x6,y0,y1,y2,y3,y4,y5,y6,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0]; \ + x0 = decode[0] * c; \ + x1 = decode[1] * c; \ + x2 = decode[2] * c; \ + x3 = decode[3] * c; \ + x4 = decode[4] * c; \ + x5 = decode[5] * c; \ + x6 = decode[6] * c; \ + c = hc[1]; \ + y0 = decode[7] * c; \ + y1 = decode[8] * c; \ + y2 = decode[9] * c; \ + y3 = decode[10] * c; \ + y4 = decode[11] * c; \ + y5 = decode[12] * c; \ + y6 = decode[13] * c; \ + c = hc[2]; \ + x0 += decode[14] * c; \ + x1 += decode[15] * c; \ + x2 += decode[16] * c; \ + x3 += decode[17] * c; \ + x4 += decode[18] * c; \ + x5 += decode[19] * c; \ + x6 += decode[20] * c; \ + c = hc[3]; \ + y0 += decode[21] * c; \ + y1 += decode[22] * c; \ + y2 += decode[23] * c; \ + y3 += decode[24] * c; \ + y4 += decode[25] * c; \ + y5 += decode[26] * c; \ + y6 += decode[27] * c; + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*7] * c; \ + x1 += decode[1+(ofs)*7] * c; \ + x2 += decode[2+(ofs)*7] * c; \ + x3 += decode[3+(ofs)*7] * c; \ + x4 += decode[4+(ofs)*7] * c; \ + x5 += decode[5+(ofs)*7] * c; \ + x6 += decode[6+(ofs)*7] * c; \ + c = hc[1+(ofs)]; \ + y0 += decode[7+(ofs)*7] * c; \ + y1 += decode[8+(ofs)*7] * c; \ + y2 += decode[9+(ofs)*7] * c; \ + y3 += decode[10+(ofs)*7] * c; \ + y4 += decode[11+(ofs)*7] * c; \ + y5 += decode[12+(ofs)*7] * c; \ + y6 += decode[13+(ofs)*7] * c; \ + c = hc[2+(ofs)]; \ + x0 += decode[14+(ofs)*7] * c; \ + x1 += decode[15+(ofs)*7] * c; \ + x2 += decode[16+(ofs)*7] * c; \ + x3 += decode[17+(ofs)*7] * c; \ + x4 += decode[18+(ofs)*7] * c; \ + x5 += decode[19+(ofs)*7] * c; \ + x6 += decode[20+(ofs)*7] * c; \ + c = hc[3+(ofs)]; \ + y0 += decode[21+(ofs)*7] * c; \ + y1 += decode[22+(ofs)*7] * c; \ + y2 += decode[23+(ofs)*7] * c; \ + y3 += decode[24+(ofs)*7] * c; \ + y4 += decode[25+(ofs)*7] * c; \ + y5 += decode[26+(ofs)*7] * c; \ + y6 += decode[27+(ofs)*7] * c; + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*7] * c; \ + x1 += decode[1+(ofs)*7] * c; \ + x2 += decode[2+(ofs)*7] * c; \ + x3 += decode[3+(ofs)*7] * c; \ + x4 += decode[4+(ofs)*7] * c; \ + x5 += decode[5+(ofs)*7] * c; \ + x6 += decode[6+(ofs)*7] * c; \ + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*7] * c; \ + x1 += decode[1+(ofs)*7] * c; \ + x2 += decode[2+(ofs)*7] * c; \ + x3 += decode[3+(ofs)*7] * c; \ + x4 += decode[4+(ofs)*7] * c; \ + x5 += decode[5+(ofs)*7] * c; \ + x6 += decode[6+(ofs)*7] * c; \ + c = hc[1+(ofs)]; \ + y0 += decode[7+(ofs)*7] * c; \ + y1 += decode[8+(ofs)*7] * c; \ + y2 += decode[9+(ofs)*7] * c; \ + y3 += decode[10+(ofs)*7] * c; \ + y4 += decode[11+(ofs)*7] * c; \ + y5 += decode[12+(ofs)*7] * c; \ + y6 += decode[13+(ofs)*7] * c; \ + +#define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*7] * c; \ + x1 += decode[1+(ofs)*7] * c; \ + x2 += decode[2+(ofs)*7] * c; \ + x3 += decode[3+(ofs)*7] * c; \ + x4 += decode[4+(ofs)*7] * c; \ + x5 += decode[5+(ofs)*7] * c; \ + x6 += decode[6+(ofs)*7] * c; \ + c = hc[1+(ofs)]; \ + y0 += decode[7+(ofs)*7] * c; \ + y1 += decode[8+(ofs)*7] * c; \ + y2 += decode[9+(ofs)*7] * c; \ + y3 += decode[10+(ofs)*7] * c; \ + y4 += decode[11+(ofs)*7] * c; \ + y5 += decode[12+(ofs)*7] * c; \ + y6 += decode[13+(ofs)*7] * c; \ + c = hc[2+(ofs)]; \ + x0 += decode[14+(ofs)*7] * c; \ + x1 += decode[15+(ofs)*7] * c; \ + x2 += decode[16+(ofs)*7] * c; \ + x3 += decode[17+(ofs)*7] * c; \ + x4 += decode[18+(ofs)*7] * c; \ + x5 += decode[19+(ofs)*7] * c; \ + x6 += decode[20+(ofs)*7] * c; \ + +#define stbir__store_output() \ + output[0] = x0 + y0; \ + output[1] = x1 + y1; \ + output[2] = x2 + y2; \ + output[3] = x3 + y3; \ + output[4] = x4 + y4; \ + output[5] = x5 + y5; \ + output[6] = x6 + y6; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 7; + +#endif + +#define STBIR__horizontal_channels 7 +#define STB_IMAGE_RESIZE_DO_HORIZONTALS +#include STBIR__HEADER_FILENAME + + +// include all of the vertical resamplers (both scatter and gather versions) + +#define STBIR__vertical_channels 1 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 1 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 2 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 2 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 3 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 3 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 4 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 4 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 5 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 5 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 6 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 6 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 7 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 7 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 8 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 8 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +typedef void STBIR_VERTICAL_GATHERFUNC( float * output, float const * coeffs, float const ** inputs, float const * input0_end ); + +static STBIR_VERTICAL_GATHERFUNC * stbir__vertical_gathers[ 8 ] = +{ + stbir__vertical_gather_with_1_coeffs,stbir__vertical_gather_with_2_coeffs,stbir__vertical_gather_with_3_coeffs,stbir__vertical_gather_with_4_coeffs,stbir__vertical_gather_with_5_coeffs,stbir__vertical_gather_with_6_coeffs,stbir__vertical_gather_with_7_coeffs,stbir__vertical_gather_with_8_coeffs +}; + +static STBIR_VERTICAL_GATHERFUNC * stbir__vertical_gathers_continues[ 8 ] = +{ + stbir__vertical_gather_with_1_coeffs_cont,stbir__vertical_gather_with_2_coeffs_cont,stbir__vertical_gather_with_3_coeffs_cont,stbir__vertical_gather_with_4_coeffs_cont,stbir__vertical_gather_with_5_coeffs_cont,stbir__vertical_gather_with_6_coeffs_cont,stbir__vertical_gather_with_7_coeffs_cont,stbir__vertical_gather_with_8_coeffs_cont +}; + +typedef void STBIR_VERTICAL_SCATTERFUNC( float ** outputs, float const * coeffs, float const * input, float const * input_end ); + +static STBIR_VERTICAL_SCATTERFUNC * stbir__vertical_scatter_sets[ 8 ] = +{ + stbir__vertical_scatter_with_1_coeffs,stbir__vertical_scatter_with_2_coeffs,stbir__vertical_scatter_with_3_coeffs,stbir__vertical_scatter_with_4_coeffs,stbir__vertical_scatter_with_5_coeffs,stbir__vertical_scatter_with_6_coeffs,stbir__vertical_scatter_with_7_coeffs,stbir__vertical_scatter_with_8_coeffs +}; + +static STBIR_VERTICAL_SCATTERFUNC * stbir__vertical_scatter_blends[ 8 ] = +{ + stbir__vertical_scatter_with_1_coeffs_cont,stbir__vertical_scatter_with_2_coeffs_cont,stbir__vertical_scatter_with_3_coeffs_cont,stbir__vertical_scatter_with_4_coeffs_cont,stbir__vertical_scatter_with_5_coeffs_cont,stbir__vertical_scatter_with_6_coeffs_cont,stbir__vertical_scatter_with_7_coeffs_cont,stbir__vertical_scatter_with_8_coeffs_cont +}; + + +static void stbir__encode_scanline( stbir__info const * stbir_info, void *output_buffer_data, float * encode_buffer, int row STBIR_ONLY_PROFILE_GET_SPLIT_INFO ) +{ + int num_pixels = stbir_info->horizontal.scale_info.output_sub_size; + int channels = stbir_info->channels; + int width_times_channels = num_pixels * channels; + void * output_buffer; + + // un-alpha weight if we need to + if ( stbir_info->alpha_unweight ) + { + STBIR_PROFILE_START( unalpha ); + stbir_info->alpha_unweight( encode_buffer, width_times_channels ); + STBIR_PROFILE_END( unalpha ); + } + + // write directly into output by default + output_buffer = output_buffer_data; + + // if we have an output callback, we first convert the decode buffer in place (and then hand that to the callback) + if ( stbir_info->out_pixels_cb ) + output_buffer = encode_buffer; + + STBIR_PROFILE_START( encode ); + // convert into the output buffer + stbir_info->encode_pixels( output_buffer, width_times_channels, encode_buffer ); + STBIR_PROFILE_END( encode ); + + // if we have an output callback, call it to send the data + if ( stbir_info->out_pixels_cb ) + stbir_info->out_pixels_cb( output_buffer, num_pixels, row, stbir_info->user_data ); +} + + +// Get the ring buffer pointer for an index +static float* stbir__get_ring_buffer_entry(stbir__info const * stbir_info, stbir__per_split_info const * split_info, int index ) +{ + STBIR_ASSERT( index < stbir_info->ring_buffer_num_entries ); + + #ifdef STBIR__SEPARATE_ALLOCATIONS + return split_info->ring_buffers[ index ]; + #else + return (float*) ( ( (char*) split_info->ring_buffer ) + ( index * stbir_info->ring_buffer_length_bytes ) ); + #endif +} + +// Get the specified scan line from the ring buffer +static float* stbir__get_ring_buffer_scanline(stbir__info const * stbir_info, stbir__per_split_info const * split_info, int get_scanline) +{ + int ring_buffer_index = (split_info->ring_buffer_begin_index + (get_scanline - split_info->ring_buffer_first_scanline)) % stbir_info->ring_buffer_num_entries; + return stbir__get_ring_buffer_entry( stbir_info, split_info, ring_buffer_index ); +} + +static void stbir__resample_horizontal_gather(stbir__info const * stbir_info, float* output_buffer, float const * input_buffer STBIR_ONLY_PROFILE_GET_SPLIT_INFO ) +{ + float const * decode_buffer = input_buffer - ( stbir_info->scanline_extents.conservative.n0 * stbir_info->effective_channels ); + + STBIR_PROFILE_START( horizontal ); + if ( ( stbir_info->horizontal.filter_enum == STBIR_FILTER_POINT_SAMPLE ) && ( stbir_info->horizontal.scale_info.scale == 1.0f ) ) + STBIR_MEMCPY( output_buffer, input_buffer, stbir_info->horizontal.scale_info.output_sub_size * sizeof( float ) * stbir_info->effective_channels ); + else + stbir_info->horizontal_gather_channels( output_buffer, stbir_info->horizontal.scale_info.output_sub_size, decode_buffer, stbir_info->horizontal.contributors, stbir_info->horizontal.coefficients, stbir_info->horizontal.coefficient_width ); + STBIR_PROFILE_END( horizontal ); +} + +static void stbir__resample_vertical_gather(stbir__info const * stbir_info, stbir__per_split_info* split_info, int n, int contrib_n0, int contrib_n1, float const * vertical_coefficients ) +{ + float* encode_buffer = split_info->vertical_buffer; + float* decode_buffer = split_info->decode_buffer; + int vertical_first = stbir_info->vertical_first; + int width = (vertical_first) ? ( stbir_info->scanline_extents.conservative.n1-stbir_info->scanline_extents.conservative.n0+1 ) : stbir_info->horizontal.scale_info.output_sub_size; + int width_times_channels = stbir_info->effective_channels * width; + + STBIR_ASSERT( stbir_info->vertical.is_gather ); + + // loop over the contributing scanlines and scale into the buffer + STBIR_PROFILE_START( vertical ); + { + int k = 0, total = contrib_n1 - contrib_n0 + 1; + STBIR_ASSERT( total > 0 ); + do { + float const * inputs[8]; + int i, cnt = total; if ( cnt > 8 ) cnt = 8; + for( i = 0 ; i < cnt ; i++ ) + inputs[ i ] = stbir__get_ring_buffer_scanline(stbir_info, split_info, k+i+contrib_n0 ); + + // call the N scanlines at a time function (up to 8 scanlines of blending at once) + ((k==0)?stbir__vertical_gathers:stbir__vertical_gathers_continues)[cnt-1]( (vertical_first) ? decode_buffer : encode_buffer, vertical_coefficients + k, inputs, inputs[0] + width_times_channels ); + k += cnt; + total -= cnt; + } while ( total ); + } + STBIR_PROFILE_END( vertical ); + + if ( vertical_first ) + { + // Now resample the gathered vertical data in the horizontal axis into the encode buffer + stbir__resample_horizontal_gather(stbir_info, encode_buffer, decode_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + } + + stbir__encode_scanline( stbir_info, ( (char *) stbir_info->output_data ) + ((ptrdiff_t)n * (ptrdiff_t)stbir_info->output_stride_bytes), + encode_buffer, n STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); +} + +static void stbir__decode_and_resample_for_vertical_gather_loop(stbir__info const * stbir_info, stbir__per_split_info* split_info, int n) +{ + int ring_buffer_index; + float* ring_buffer; + + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline( stbir_info, n, split_info->decode_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // update new end scanline + split_info->ring_buffer_last_scanline = n; + + // get ring buffer + ring_buffer_index = (split_info->ring_buffer_begin_index + (split_info->ring_buffer_last_scanline - split_info->ring_buffer_first_scanline)) % stbir_info->ring_buffer_num_entries; + ring_buffer = stbir__get_ring_buffer_entry(stbir_info, split_info, ring_buffer_index); + + // Now resample it into the ring buffer. + stbir__resample_horizontal_gather( stbir_info, ring_buffer, split_info->decode_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // Now it's sitting in the ring buffer ready to be used as source for the vertical sampling. +} + +static void stbir__vertical_gather_loop( stbir__info const * stbir_info, stbir__per_split_info* split_info, int split_count ) +{ + int y, start_output_y, end_output_y; + stbir__contributors* vertical_contributors = stbir_info->vertical.contributors; + float const * vertical_coefficients = stbir_info->vertical.coefficients; + + STBIR_ASSERT( stbir_info->vertical.is_gather ); + + start_output_y = split_info->start_output_y; + end_output_y = split_info[split_count-1].end_output_y; + + vertical_contributors += start_output_y; + vertical_coefficients += start_output_y * stbir_info->vertical.coefficient_width; + + // initialize the ring buffer for gathering + split_info->ring_buffer_begin_index = 0; + split_info->ring_buffer_first_scanline = stbir_info->vertical.extent_info.lowest; + split_info->ring_buffer_last_scanline = split_info->ring_buffer_first_scanline - 1; // means "empty" + + for (y = start_output_y; y < end_output_y; y++) + { + int in_first_scanline, in_last_scanline; + + in_first_scanline = vertical_contributors->n0; + in_last_scanline = vertical_contributors->n1; + + // make sure the indexing hasn't broken + STBIR_ASSERT( in_first_scanline >= split_info->ring_buffer_first_scanline ); + + // Load in new scanlines + while (in_last_scanline > split_info->ring_buffer_last_scanline) + { + STBIR_ASSERT( ( split_info->ring_buffer_last_scanline - split_info->ring_buffer_first_scanline + 1 ) <= stbir_info->ring_buffer_num_entries ); + + // make sure there was room in the ring buffer when we add new scanlines + if ( ( split_info->ring_buffer_last_scanline - split_info->ring_buffer_first_scanline + 1 ) == stbir_info->ring_buffer_num_entries ) + { + split_info->ring_buffer_first_scanline++; + split_info->ring_buffer_begin_index++; + } + + if ( stbir_info->vertical_first ) + { + float * ring_buffer = stbir__get_ring_buffer_scanline( stbir_info, split_info, ++split_info->ring_buffer_last_scanline ); + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline( stbir_info, split_info->ring_buffer_last_scanline, ring_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + } + else + { + stbir__decode_and_resample_for_vertical_gather_loop(stbir_info, split_info, split_info->ring_buffer_last_scanline + 1); + } + } + + // Now all buffers should be ready to write a row of vertical sampling, so do it. + stbir__resample_vertical_gather(stbir_info, split_info, y, in_first_scanline, in_last_scanline, vertical_coefficients ); + + ++vertical_contributors; + vertical_coefficients += stbir_info->vertical.coefficient_width; + } +} + +#define STBIR__FLOAT_EMPTY_MARKER 3.0e+38F +#define STBIR__FLOAT_BUFFER_IS_EMPTY(ptr) ((ptr)[0]==STBIR__FLOAT_EMPTY_MARKER) + +static void stbir__encode_first_scanline_from_scatter(stbir__info const * stbir_info, stbir__per_split_info* split_info) +{ + // evict a scanline out into the output buffer + float* ring_buffer_entry = stbir__get_ring_buffer_entry(stbir_info, split_info, split_info->ring_buffer_begin_index ); + + // dump the scanline out + stbir__encode_scanline( stbir_info, ( (char *)stbir_info->output_data ) + ( (ptrdiff_t)split_info->ring_buffer_first_scanline * (ptrdiff_t)stbir_info->output_stride_bytes ), ring_buffer_entry, split_info->ring_buffer_first_scanline STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // mark it as empty + ring_buffer_entry[ 0 ] = STBIR__FLOAT_EMPTY_MARKER; + + // advance the first scanline + split_info->ring_buffer_first_scanline++; + if ( ++split_info->ring_buffer_begin_index == stbir_info->ring_buffer_num_entries ) + split_info->ring_buffer_begin_index = 0; +} + +static void stbir__horizontal_resample_and_encode_first_scanline_from_scatter(stbir__info const * stbir_info, stbir__per_split_info* split_info) +{ + // evict a scanline out into the output buffer + + float* ring_buffer_entry = stbir__get_ring_buffer_entry(stbir_info, split_info, split_info->ring_buffer_begin_index ); + + // Now resample it into the buffer. + stbir__resample_horizontal_gather( stbir_info, split_info->vertical_buffer, ring_buffer_entry STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // dump the scanline out + stbir__encode_scanline( stbir_info, ( (char *)stbir_info->output_data ) + ( (ptrdiff_t)split_info->ring_buffer_first_scanline * (ptrdiff_t)stbir_info->output_stride_bytes ), split_info->vertical_buffer, split_info->ring_buffer_first_scanline STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // mark it as empty + ring_buffer_entry[ 0 ] = STBIR__FLOAT_EMPTY_MARKER; + + // advance the first scanline + split_info->ring_buffer_first_scanline++; + if ( ++split_info->ring_buffer_begin_index == stbir_info->ring_buffer_num_entries ) + split_info->ring_buffer_begin_index = 0; +} + +static void stbir__resample_vertical_scatter(stbir__info const * stbir_info, stbir__per_split_info* split_info, int n0, int n1, float const * vertical_coefficients, float const * vertical_buffer, float const * vertical_buffer_end ) +{ + STBIR_ASSERT( !stbir_info->vertical.is_gather ); + + STBIR_PROFILE_START( vertical ); + { + int k = 0, total = n1 - n0 + 1; + STBIR_ASSERT( total > 0 ); + do { + float * outputs[8]; + int i, n = total; if ( n > 8 ) n = 8; + for( i = 0 ; i < n ; i++ ) + { + outputs[ i ] = stbir__get_ring_buffer_scanline(stbir_info, split_info, k+i+n0 ); + if ( ( i ) && ( STBIR__FLOAT_BUFFER_IS_EMPTY( outputs[i] ) != STBIR__FLOAT_BUFFER_IS_EMPTY( outputs[0] ) ) ) // make sure runs are of the same type + { + n = i; + break; + } + } + // call the scatter to N scanlines at a time function (up to 8 scanlines of scattering at once) + ((STBIR__FLOAT_BUFFER_IS_EMPTY( outputs[0] ))?stbir__vertical_scatter_sets:stbir__vertical_scatter_blends)[n-1]( outputs, vertical_coefficients + k, vertical_buffer, vertical_buffer_end ); + k += n; + total -= n; + } while ( total ); + } + + STBIR_PROFILE_END( vertical ); +} + +typedef void stbir__handle_scanline_for_scatter_func(stbir__info const * stbir_info, stbir__per_split_info* split_info); + +static void stbir__vertical_scatter_loop( stbir__info const * stbir_info, stbir__per_split_info* split_info, int split_count ) +{ + int y, start_output_y, end_output_y, start_input_y, end_input_y; + stbir__contributors* vertical_contributors = stbir_info->vertical.contributors; + float const * vertical_coefficients = stbir_info->vertical.coefficients; + stbir__handle_scanline_for_scatter_func * handle_scanline_for_scatter; + void * scanline_scatter_buffer; + void * scanline_scatter_buffer_end; + int on_first_input_y, last_input_y; + + STBIR_ASSERT( !stbir_info->vertical.is_gather ); + + start_output_y = split_info->start_output_y; + end_output_y = split_info[split_count-1].end_output_y; // may do multiple split counts + + start_input_y = split_info->start_input_y; + end_input_y = split_info[split_count-1].end_input_y; + + // adjust for starting offset start_input_y + y = start_input_y + stbir_info->vertical.filter_pixel_margin; + vertical_contributors += y ; + vertical_coefficients += stbir_info->vertical.coefficient_width * y; + + if ( stbir_info->vertical_first ) + { + handle_scanline_for_scatter = stbir__horizontal_resample_and_encode_first_scanline_from_scatter; + scanline_scatter_buffer = split_info->decode_buffer; + scanline_scatter_buffer_end = ( (char*) scanline_scatter_buffer ) + sizeof( float ) * stbir_info->effective_channels * (stbir_info->scanline_extents.conservative.n1-stbir_info->scanline_extents.conservative.n0+1); + } + else + { + handle_scanline_for_scatter = stbir__encode_first_scanline_from_scatter; + scanline_scatter_buffer = split_info->vertical_buffer; + scanline_scatter_buffer_end = ( (char*) scanline_scatter_buffer ) + sizeof( float ) * stbir_info->effective_channels * stbir_info->horizontal.scale_info.output_sub_size; + } + + // initialize the ring buffer for scattering + split_info->ring_buffer_first_scanline = start_output_y; + split_info->ring_buffer_last_scanline = -1; + split_info->ring_buffer_begin_index = -1; + + // mark all the buffers as empty to start + for( y = 0 ; y < stbir_info->ring_buffer_num_entries ; y++ ) + stbir__get_ring_buffer_entry( stbir_info, split_info, y )[0] = STBIR__FLOAT_EMPTY_MARKER; // only used on scatter + + // do the loop in input space + on_first_input_y = 1; last_input_y = start_input_y; + for (y = start_input_y ; y < end_input_y; y++) + { + int out_first_scanline, out_last_scanline; + + out_first_scanline = vertical_contributors->n0; + out_last_scanline = vertical_contributors->n1; + + STBIR_ASSERT(out_last_scanline - out_first_scanline + 1 <= stbir_info->ring_buffer_num_entries); + + if ( ( out_last_scanline >= out_first_scanline ) && ( ( ( out_first_scanline >= start_output_y ) && ( out_first_scanline < end_output_y ) ) || ( ( out_last_scanline >= start_output_y ) && ( out_last_scanline < end_output_y ) ) ) ) + { + float const * vc = vertical_coefficients; + + // keep track of the range actually seen for the next resize + last_input_y = y; + if ( ( on_first_input_y ) && ( y > start_input_y ) ) + split_info->start_input_y = y; + on_first_input_y = 0; + + // clip the region + if ( out_first_scanline < start_output_y ) + { + vc += start_output_y - out_first_scanline; + out_first_scanline = start_output_y; + } + + if ( out_last_scanline >= end_output_y ) + out_last_scanline = end_output_y - 1; + + // if very first scanline, init the index + if (split_info->ring_buffer_begin_index < 0) + split_info->ring_buffer_begin_index = out_first_scanline - start_output_y; + + STBIR_ASSERT( split_info->ring_buffer_begin_index <= out_first_scanline ); + + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline( stbir_info, y, split_info->decode_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // When horizontal first, we resample horizontally into the vertical buffer before we scatter it out + if ( !stbir_info->vertical_first ) + stbir__resample_horizontal_gather( stbir_info, split_info->vertical_buffer, split_info->decode_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // Now it's sitting in the buffer ready to be distributed into the ring buffers. + + // evict from the ringbuffer, if we need are full + if ( ( ( split_info->ring_buffer_last_scanline - split_info->ring_buffer_first_scanline + 1 ) == stbir_info->ring_buffer_num_entries ) && + ( out_last_scanline > split_info->ring_buffer_last_scanline ) ) + handle_scanline_for_scatter( stbir_info, split_info ); + + // Now the horizontal buffer is ready to write to all ring buffer rows, so do it. + stbir__resample_vertical_scatter(stbir_info, split_info, out_first_scanline, out_last_scanline, vc, (float*)scanline_scatter_buffer, (float*)scanline_scatter_buffer_end ); + + // update the end of the buffer + if ( out_last_scanline > split_info->ring_buffer_last_scanline ) + split_info->ring_buffer_last_scanline = out_last_scanline; + } + ++vertical_contributors; + vertical_coefficients += stbir_info->vertical.coefficient_width; + } + + // now evict the scanlines that are left over in the ring buffer + while ( split_info->ring_buffer_first_scanline < end_output_y ) + handle_scanline_for_scatter(stbir_info, split_info); + + // update the end_input_y if we do multiple resizes with the same data + ++last_input_y; + for( y = 0 ; y < split_count; y++ ) + if ( split_info[y].end_input_y > last_input_y ) + split_info[y].end_input_y = last_input_y; +} + + +static stbir__kernel_callback * stbir__builtin_kernels[] = { 0, stbir__filter_trapezoid, stbir__filter_triangle, stbir__filter_cubic, stbir__filter_catmullrom, stbir__filter_mitchell, stbir__filter_point }; +static stbir__support_callback * stbir__builtin_supports[] = { 0, stbir__support_trapezoid, stbir__support_one, stbir__support_two, stbir__support_two, stbir__support_two, stbir__support_zeropoint5 }; + +static void stbir__set_sampler(stbir__sampler * samp, stbir_filter filter, stbir__kernel_callback * kernel, stbir__support_callback * support, stbir_edge edge, stbir__scale_info * scale_info, int always_gather, void * user_data ) +{ + // set filter + if (filter == 0) + { + filter = STBIR_DEFAULT_FILTER_DOWNSAMPLE; // default to downsample + if (scale_info->scale >= ( 1.0f - stbir__small_float ) ) + { + if ( (scale_info->scale <= ( 1.0f + stbir__small_float ) ) && ( STBIR_CEILF(scale_info->pixel_shift) == scale_info->pixel_shift ) ) + filter = STBIR_FILTER_POINT_SAMPLE; + else + filter = STBIR_DEFAULT_FILTER_UPSAMPLE; + } + } + samp->filter_enum = filter; + + STBIR_ASSERT(samp->filter_enum != 0); + STBIR_ASSERT((unsigned)samp->filter_enum < STBIR_FILTER_OTHER); + samp->filter_kernel = stbir__builtin_kernels[ filter ]; + samp->filter_support = stbir__builtin_supports[ filter ]; + + if ( kernel && support ) + { + samp->filter_kernel = kernel; + samp->filter_support = support; + samp->filter_enum = STBIR_FILTER_OTHER; + } + + samp->edge = edge; + samp->filter_pixel_width = stbir__get_filter_pixel_width (samp->filter_support, scale_info->scale, user_data ); + // Gather is always better, but in extreme downsamples, you have to most or all of the data in memory + // For horizontal, we always have all the pixels, so we always use gather here (always_gather==1). + // For vertical, we use gather if scaling up (which means we will have samp->filter_pixel_width + // scanlines in memory at once). + samp->is_gather = 0; + if ( scale_info->scale >= ( 1.0f - stbir__small_float ) ) + samp->is_gather = 1; + else if ( ( always_gather ) || ( samp->filter_pixel_width <= STBIR_FORCE_GATHER_FILTER_SCANLINES_AMOUNT ) ) + samp->is_gather = 2; + + // pre calculate stuff based on the above + samp->coefficient_width = stbir__get_coefficient_width(samp, samp->is_gather, user_data); + + // filter_pixel_width is the conservative size in pixels of input that affect an output pixel. + // In rare cases (only with 2 pix to 1 pix with the default filters), it's possible that the + // filter will extend before or after the scanline beyond just one extra entire copy of the + // scanline (we would hit the edge twice). We don't let you do that, so we clamp the total + // width to 3x the total of input pixel (once for the scanline, once for the left side + // overhang, and once for the right side). We only do this for edge mode, since the other + // modes can just re-edge clamp back in again. + if ( edge == STBIR_EDGE_WRAP ) + if ( samp->filter_pixel_width > ( scale_info->input_full_size * 3 ) ) + samp->filter_pixel_width = scale_info->input_full_size * 3; + + // This is how much to expand buffers to account for filters seeking outside + // the image boundaries. + samp->filter_pixel_margin = samp->filter_pixel_width / 2; + + // filter_pixel_margin is the amount that this filter can overhang on just one side of either + // end of the scanline (left or the right). Since we only allow you to overhang 1 scanline's + // worth of pixels, we clamp this one side of overhang to the input scanline size. Again, + // this clamping only happens in rare cases with the default filters (2 pix to 1 pix). + if ( edge == STBIR_EDGE_WRAP ) + if ( samp->filter_pixel_margin > scale_info->input_full_size ) + samp->filter_pixel_margin = scale_info->input_full_size; + + samp->num_contributors = stbir__get_contributors(samp, samp->is_gather); + + samp->contributors_size = samp->num_contributors * sizeof(stbir__contributors); + samp->coefficients_size = samp->num_contributors * samp->coefficient_width * sizeof(float) + sizeof(float); // extra sizeof(float) is padding + + samp->gather_prescatter_contributors = 0; + samp->gather_prescatter_coefficients = 0; + if ( samp->is_gather == 0 ) + { + samp->gather_prescatter_coefficient_width = samp->filter_pixel_width; + samp->gather_prescatter_num_contributors = stbir__get_contributors(samp, 2); + samp->gather_prescatter_contributors_size = samp->gather_prescatter_num_contributors * sizeof(stbir__contributors); + samp->gather_prescatter_coefficients_size = samp->gather_prescatter_num_contributors * samp->gather_prescatter_coefficient_width * sizeof(float); + } +} + +static void stbir__get_conservative_extents( stbir__sampler * samp, stbir__contributors * range, void * user_data ) +{ + float scale = samp->scale_info.scale; + float out_shift = samp->scale_info.pixel_shift; + stbir__support_callback * support = samp->filter_support; + int input_full_size = samp->scale_info.input_full_size; + stbir_edge edge = samp->edge; + float inv_scale = samp->scale_info.inv_scale; + + STBIR_ASSERT( samp->is_gather != 0 ); + + if ( samp->is_gather == 1 ) + { + int in_first_pixel, in_last_pixel; + float out_filter_radius = support(inv_scale, user_data) * scale; + + stbir__calculate_in_pixel_range( &in_first_pixel, &in_last_pixel, 0.5, out_filter_radius, inv_scale, out_shift, input_full_size, edge ); + range->n0 = in_first_pixel; + stbir__calculate_in_pixel_range( &in_first_pixel, &in_last_pixel, ( (float)(samp->scale_info.output_sub_size-1) ) + 0.5f, out_filter_radius, inv_scale, out_shift, input_full_size, edge ); + range->n1 = in_last_pixel; + } + else if ( samp->is_gather == 2 ) // downsample gather, refine + { + float in_pixels_radius = support(scale, user_data) * inv_scale; + int filter_pixel_margin = samp->filter_pixel_margin; + int output_sub_size = samp->scale_info.output_sub_size; + int input_end; + int n; + int in_first_pixel, in_last_pixel; + + // get a conservative area of the input range + stbir__calculate_in_pixel_range( &in_first_pixel, &in_last_pixel, 0, 0, inv_scale, out_shift, input_full_size, edge ); + range->n0 = in_first_pixel; + stbir__calculate_in_pixel_range( &in_first_pixel, &in_last_pixel, (float)output_sub_size, 0, inv_scale, out_shift, input_full_size, edge ); + range->n1 = in_last_pixel; + + // now go through the margin to the start of area to find bottom + n = range->n0 + 1; + input_end = -filter_pixel_margin; + while( n >= input_end ) + { + int out_first_pixel, out_last_pixel; + stbir__calculate_out_pixel_range( &out_first_pixel, &out_last_pixel, ((float)n)+0.5f, in_pixels_radius, scale, out_shift, output_sub_size ); + if ( out_first_pixel > out_last_pixel ) + break; + + if ( ( out_first_pixel < output_sub_size ) || ( out_last_pixel >= 0 ) ) + range->n0 = n; + --n; + } + + // now go through the end of the area through the margin to find top + n = range->n1 - 1; + input_end = n + 1 + filter_pixel_margin; + while( n <= input_end ) + { + int out_first_pixel, out_last_pixel; + stbir__calculate_out_pixel_range( &out_first_pixel, &out_last_pixel, ((float)n)+0.5f, in_pixels_radius, scale, out_shift, output_sub_size ); + if ( out_first_pixel > out_last_pixel ) + break; + if ( ( out_first_pixel < output_sub_size ) || ( out_last_pixel >= 0 ) ) + range->n1 = n; + ++n; + } + } + + if ( samp->edge == STBIR_EDGE_WRAP ) + { + // if we are wrapping, and we are very close to the image size (so the edges might merge), just use the scanline up to the edge + if ( ( range->n0 > 0 ) && ( range->n1 >= input_full_size ) ) + { + int marg = range->n1 - input_full_size + 1; + if ( ( marg + STBIR__MERGE_RUNS_PIXEL_THRESHOLD ) >= range->n0 ) + range->n0 = 0; + } + if ( ( range->n0 < 0 ) && ( range->n1 < (input_full_size-1) ) ) + { + int marg = -range->n0; + if ( ( input_full_size - marg - STBIR__MERGE_RUNS_PIXEL_THRESHOLD - 1 ) <= range->n1 ) + range->n1 = input_full_size - 1; + } + } + else + { + // for non-edge-wrap modes, we never read over the edge, so clamp + if ( range->n0 < 0 ) + range->n0 = 0; + if ( range->n1 >= input_full_size ) + range->n1 = input_full_size - 1; + } +} + +static void stbir__get_split_info( stbir__per_split_info* split_info, int splits, int output_height, int vertical_pixel_margin, int input_full_height ) +{ + int i, cur; + int left = output_height; + + cur = 0; + for( i = 0 ; i < splits ; i++ ) + { + int each; + split_info[i].start_output_y = cur; + each = left / ( splits - i ); + split_info[i].end_output_y = cur + each; + cur += each; + left -= each; + + // scatter range (updated to minimum as you run it) + split_info[i].start_input_y = -vertical_pixel_margin; + split_info[i].end_input_y = input_full_height + vertical_pixel_margin; + } +} + +static void stbir__free_internal_mem( stbir__info *info ) +{ + #define STBIR__FREE_AND_CLEAR( ptr ) { if ( ptr ) { void * p = (ptr); (ptr) = 0; STBIR_FREE( p, info->user_data); } } + + if ( info ) + { + #ifndef STBIR__SEPARATE_ALLOCATIONS + STBIR__FREE_AND_CLEAR( info->alloced_mem ); + #else + int i,j; + + if ( ( info->vertical.gather_prescatter_contributors ) && ( (void*)info->vertical.gather_prescatter_contributors != (void*)info->split_info[0].decode_buffer ) ) + { + STBIR__FREE_AND_CLEAR( info->vertical.gather_prescatter_coefficients ); + STBIR__FREE_AND_CLEAR( info->vertical.gather_prescatter_contributors ); + } + for( i = 0 ; i < info->splits ; i++ ) + { + for( j = 0 ; j < info->alloc_ring_buffer_num_entries ; j++ ) + { + #ifdef STBIR_SIMD8 + if ( info->effective_channels == 3 ) + --info->split_info[i].ring_buffers[j]; // avx in 3 channel mode needs one float at the start of the buffer + #endif + STBIR__FREE_AND_CLEAR( info->split_info[i].ring_buffers[j] ); + } + + #ifdef STBIR_SIMD8 + if ( info->effective_channels == 3 ) + --info->split_info[i].decode_buffer; // avx in 3 channel mode needs one float at the start of the buffer + #endif + STBIR__FREE_AND_CLEAR( info->split_info[i].decode_buffer ); + STBIR__FREE_AND_CLEAR( info->split_info[i].ring_buffers ); + STBIR__FREE_AND_CLEAR( info->split_info[i].vertical_buffer ); + } + STBIR__FREE_AND_CLEAR( info->split_info ); + if ( info->vertical.coefficients != info->horizontal.coefficients ) + { + STBIR__FREE_AND_CLEAR( info->vertical.coefficients ); + STBIR__FREE_AND_CLEAR( info->vertical.contributors ); + } + STBIR__FREE_AND_CLEAR( info->horizontal.coefficients ); + STBIR__FREE_AND_CLEAR( info->horizontal.contributors ); + STBIR__FREE_AND_CLEAR( info->alloced_mem ); + STBIR__FREE_AND_CLEAR( info ); + #endif + } + + #undef STBIR__FREE_AND_CLEAR +} + +static int stbir__get_max_split( int splits, int height ) +{ + int i; + int max = 0; + + for( i = 0 ; i < splits ; i++ ) + { + int each = height / ( splits - i ); + if ( each > max ) + max = each; + height -= each; + } + return max; +} + +static stbir__horizontal_gather_channels_func ** stbir__horizontal_gather_n_coeffs_funcs[8] = +{ + 0, stbir__horizontal_gather_1_channels_with_n_coeffs_funcs, stbir__horizontal_gather_2_channels_with_n_coeffs_funcs, stbir__horizontal_gather_3_channels_with_n_coeffs_funcs, stbir__horizontal_gather_4_channels_with_n_coeffs_funcs, 0,0, stbir__horizontal_gather_7_channels_with_n_coeffs_funcs +}; + +static stbir__horizontal_gather_channels_func ** stbir__horizontal_gather_channels_funcs[8] = +{ + 0, stbir__horizontal_gather_1_channels_funcs, stbir__horizontal_gather_2_channels_funcs, stbir__horizontal_gather_3_channels_funcs, stbir__horizontal_gather_4_channels_funcs, 0,0, stbir__horizontal_gather_7_channels_funcs +}; + +// there are six resize classifications: 0 == vertical scatter, 1 == vertical gather < 1x scale, 2 == vertical gather 1x-2x scale, 4 == vertical gather < 3x scale, 4 == vertical gather > 3x scale, 5 == <=4 pixel height, 6 == <=4 pixel wide column +#define STBIR_RESIZE_CLASSIFICATIONS 8 + +static float stbir__compute_weights[5][STBIR_RESIZE_CLASSIFICATIONS][4]= // 5 = 0=1chan, 1=2chan, 2=3chan, 3=4chan, 4=7chan +{ + { + { 1.00000f, 1.00000f, 0.31250f, 1.00000f }, + { 0.56250f, 0.59375f, 0.00000f, 0.96875f }, + { 1.00000f, 0.06250f, 0.00000f, 1.00000f }, + { 0.00000f, 0.09375f, 1.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 0.03125f, 0.12500f, 1.00000f, 1.00000f }, + { 0.06250f, 0.12500f, 0.00000f, 1.00000f }, + { 0.00000f, 1.00000f, 0.00000f, 0.03125f }, + }, { + { 0.00000f, 0.84375f, 0.00000f, 0.03125f }, + { 0.09375f, 0.93750f, 0.00000f, 0.78125f }, + { 0.87500f, 0.21875f, 0.00000f, 0.96875f }, + { 0.09375f, 0.09375f, 1.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 0.03125f, 0.12500f, 1.00000f, 1.00000f }, + { 0.06250f, 0.12500f, 0.00000f, 1.00000f }, + { 0.00000f, 1.00000f, 0.00000f, 0.53125f }, + }, { + { 0.00000f, 0.53125f, 0.00000f, 0.03125f }, + { 0.06250f, 0.96875f, 0.00000f, 0.53125f }, + { 0.87500f, 0.18750f, 0.00000f, 0.93750f }, + { 0.00000f, 0.09375f, 1.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 0.03125f, 0.12500f, 1.00000f, 1.00000f }, + { 0.06250f, 0.12500f, 0.00000f, 1.00000f }, + { 0.00000f, 1.00000f, 0.00000f, 0.56250f }, + }, { + { 0.00000f, 0.50000f, 0.00000f, 0.71875f }, + { 0.06250f, 0.84375f, 0.00000f, 0.87500f }, + { 1.00000f, 0.50000f, 0.50000f, 0.96875f }, + { 1.00000f, 0.09375f, 0.31250f, 0.50000f }, + { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 1.00000f, 0.03125f, 0.03125f, 0.53125f }, + { 0.18750f, 0.12500f, 0.00000f, 1.00000f }, + { 0.00000f, 1.00000f, 0.03125f, 0.18750f }, + }, { + { 0.00000f, 0.59375f, 0.00000f, 0.96875f }, + { 0.06250f, 0.81250f, 0.06250f, 0.59375f }, + { 0.75000f, 0.43750f, 0.12500f, 0.96875f }, + { 0.87500f, 0.06250f, 0.18750f, 0.43750f }, + { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 0.15625f, 0.12500f, 1.00000f, 1.00000f }, + { 0.06250f, 0.12500f, 0.00000f, 1.00000f }, + { 0.00000f, 1.00000f, 0.03125f, 0.34375f }, + } +}; + +// structure that allow us to query and override info for training the costs +typedef struct STBIR__V_FIRST_INFO +{ + double v_cost, h_cost; + int control_v_first; // 0 = no control, 1 = force hori, 2 = force vert + int v_first; + int v_resize_classification; + int is_gather; +} STBIR__V_FIRST_INFO; + +#ifdef STBIR__V_FIRST_INFO_BUFFER +static STBIR__V_FIRST_INFO STBIR__V_FIRST_INFO_BUFFER = {0}; +#define STBIR__V_FIRST_INFO_POINTER &STBIR__V_FIRST_INFO_BUFFER +#else +#define STBIR__V_FIRST_INFO_POINTER 0 +#endif + +// Figure out whether to scale along the horizontal or vertical first. +// This only *super* important when you are scaling by a massively +// different amount in the vertical vs the horizontal (for example, if +// you are scaling by 2x in the width, and 0.5x in the height, then you +// want to do the vertical scale first, because it's around 3x faster +// in that order. +// +// In more normal circumstances, this makes a 20-40% differences, so +// it's good to get right, but not critical. The normal way that you +// decide which direction goes first is just figuring out which +// direction does more multiplies. But with modern CPUs with their +// fancy caches and SIMD and high IPC abilities, so there's just a lot +// more that goes into it. +// +// My handwavy sort of solution is to have an app that does a whole +// bunch of timing for both vertical and horizontal first modes, +// and then another app that can read lots of these timing files +// and try to search for the best weights to use. Dotimings.c +// is the app that does a bunch of timings, and vf_train.c is the +// app that solves for the best weights (and shows how well it +// does currently). + +static int stbir__should_do_vertical_first( float weights_table[STBIR_RESIZE_CLASSIFICATIONS][4], int horizontal_filter_pixel_width, float horizontal_scale, int horizontal_output_size, int vertical_filter_pixel_width, float vertical_scale, int vertical_output_size, int is_gather, STBIR__V_FIRST_INFO * info ) +{ + double v_cost, h_cost; + float * weights; + int vertical_first; + int v_classification; + + // categorize the resize into buckets + if ( ( vertical_output_size <= 4 ) || ( horizontal_output_size <= 4 ) ) + v_classification = ( vertical_output_size < horizontal_output_size ) ? 6 : 7; + else if ( vertical_scale <= 1.0f ) + v_classification = ( is_gather ) ? 1 : 0; + else if ( vertical_scale <= 2.0f) + v_classification = 2; + else if ( vertical_scale <= 3.0f) + v_classification = 3; + else if ( vertical_scale <= 4.0f) + v_classification = 5; + else + v_classification = 6; + + // use the right weights + weights = weights_table[ v_classification ]; + + // this is the costs when you don't take into account modern CPUs with high ipc and simd and caches - wish we had a better estimate + h_cost = (float)horizontal_filter_pixel_width * weights[0] + horizontal_scale * (float)vertical_filter_pixel_width * weights[1]; + v_cost = (float)vertical_filter_pixel_width * weights[2] + vertical_scale * (float)horizontal_filter_pixel_width * weights[3]; + + // use computation estimate to decide vertical first or not + vertical_first = ( v_cost <= h_cost ) ? 1 : 0; + + // save these, if requested + if ( info ) + { + info->h_cost = h_cost; + info->v_cost = v_cost; + info->v_resize_classification = v_classification; + info->v_first = vertical_first; + info->is_gather = is_gather; + } + + // and this allows us to override everything for testing (see dotiming.c) + if ( ( info ) && ( info->control_v_first ) ) + vertical_first = ( info->control_v_first == 2 ) ? 1 : 0; + + return vertical_first; +} + +// layout lookups - must match stbir_internal_pixel_layout +static unsigned char stbir__pixel_channels[] = { + 1,2,3,3,4, // 1ch, 2ch, rgb, bgr, 4ch + 4,4,4,4,2,2, // RGBA,BGRA,ARGB,ABGR,RA,AR + 4,4,4,4,2,2, // RGBA_PM,BGRA_PM,ARGB_PM,ABGR_PM,RA_PM,AR_PM +}; + +// the internal pixel layout enums are in a different order, so we can easily do range comparisons of types +// the public pixel layout is ordered in a way that if you cast num_channels (1-4) to the enum, you get something sensible +static stbir_internal_pixel_layout stbir__pixel_layout_convert_public_to_internal[] = { + STBIRI_BGR, STBIRI_1CHANNEL, STBIRI_2CHANNEL, STBIRI_RGB, STBIRI_RGBA, + STBIRI_4CHANNEL, STBIRI_BGRA, STBIRI_ARGB, STBIRI_ABGR, STBIRI_RA, STBIRI_AR, + STBIRI_RGBA_PM, STBIRI_BGRA_PM, STBIRI_ARGB_PM, STBIRI_ABGR_PM, STBIRI_RA_PM, STBIRI_AR_PM, +}; + +static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sampler * horizontal, stbir__sampler * vertical, stbir__contributors * conservative, stbir_pixel_layout input_pixel_layout_public, stbir_pixel_layout output_pixel_layout_public, int splits, int new_x, int new_y, int fast_alpha, void * user_data STBIR_ONLY_PROFILE_BUILD_GET_INFO ) +{ + static char stbir_channel_count_index[8]={ 9,0,1,2, 3,9,9,4 }; + + stbir__info * info = 0; + void * alloced = 0; + int alloced_total = 0; + int vertical_first; + int decode_buffer_size, ring_buffer_length_bytes, ring_buffer_size, vertical_buffer_size, alloc_ring_buffer_num_entries; + + int alpha_weighting_type = 0; // 0=none, 1=simple, 2=fancy + int conservative_split_output_size = stbir__get_max_split( splits, vertical->scale_info.output_sub_size ); + stbir_internal_pixel_layout input_pixel_layout = stbir__pixel_layout_convert_public_to_internal[ input_pixel_layout_public ]; + stbir_internal_pixel_layout output_pixel_layout = stbir__pixel_layout_convert_public_to_internal[ output_pixel_layout_public ]; + int channels = stbir__pixel_channels[ input_pixel_layout ]; + int effective_channels = channels; + + // first figure out what type of alpha weighting to use (if any) + if ( ( horizontal->filter_enum != STBIR_FILTER_POINT_SAMPLE ) || ( vertical->filter_enum != STBIR_FILTER_POINT_SAMPLE ) ) // no alpha weighting on point sampling + { + if ( ( input_pixel_layout >= STBIRI_RGBA ) && ( input_pixel_layout <= STBIRI_AR ) && ( output_pixel_layout >= STBIRI_RGBA ) && ( output_pixel_layout <= STBIRI_AR ) ) + { + if ( fast_alpha ) + { + alpha_weighting_type = 4; + } + else + { + static int fancy_alpha_effective_cnts[6] = { 7, 7, 7, 7, 3, 3 }; + alpha_weighting_type = 2; + effective_channels = fancy_alpha_effective_cnts[ input_pixel_layout - STBIRI_RGBA ]; + } + } + else if ( ( input_pixel_layout >= STBIRI_RGBA_PM ) && ( input_pixel_layout <= STBIRI_AR_PM ) && ( output_pixel_layout >= STBIRI_RGBA ) && ( output_pixel_layout <= STBIRI_AR ) ) + { + // input premult, output non-premult + alpha_weighting_type = 3; + } + else if ( ( input_pixel_layout >= STBIRI_RGBA ) && ( input_pixel_layout <= STBIRI_AR ) && ( output_pixel_layout >= STBIRI_RGBA_PM ) && ( output_pixel_layout <= STBIRI_AR_PM ) ) + { + // input non-premult, output premult + alpha_weighting_type = 1; + } + } + + // channel in and out count must match currently + if ( channels != stbir__pixel_channels[ output_pixel_layout ] ) + return 0; + + // get vertical first + vertical_first = stbir__should_do_vertical_first( stbir__compute_weights[ (int)stbir_channel_count_index[ effective_channels ] ], horizontal->filter_pixel_width, horizontal->scale_info.scale, horizontal->scale_info.output_sub_size, vertical->filter_pixel_width, vertical->scale_info.scale, vertical->scale_info.output_sub_size, vertical->is_gather, STBIR__V_FIRST_INFO_POINTER ); + + // sometimes read one float off in some of the unrolled loops (with a weight of zero coeff, so it doesn't have an effect) + decode_buffer_size = ( conservative->n1 - conservative->n0 + 1 ) * effective_channels * sizeof(float) + sizeof(float); // extra float for padding + +#if defined( STBIR__SEPARATE_ALLOCATIONS ) && defined(STBIR_SIMD8) + if ( effective_channels == 3 ) + decode_buffer_size += sizeof(float); // avx in 3 channel mode needs one float at the start of the buffer (only with separate allocations) +#endif + + ring_buffer_length_bytes = horizontal->scale_info.output_sub_size * effective_channels * sizeof(float) + sizeof(float); // extra float for padding + + // if we do vertical first, the ring buffer holds a whole decoded line + if ( vertical_first ) + ring_buffer_length_bytes = ( decode_buffer_size + 15 ) & ~15; + + if ( ( ring_buffer_length_bytes & 4095 ) == 0 ) ring_buffer_length_bytes += 64*3; // avoid 4k alias + + // One extra entry because floating point precision problems sometimes cause an extra to be necessary. + alloc_ring_buffer_num_entries = vertical->filter_pixel_width + 1; + + // we never need more ring buffer entries than the scanlines we're outputting when in scatter mode + if ( ( !vertical->is_gather ) && ( alloc_ring_buffer_num_entries > conservative_split_output_size ) ) + alloc_ring_buffer_num_entries = conservative_split_output_size; + + ring_buffer_size = alloc_ring_buffer_num_entries * ring_buffer_length_bytes; + + // The vertical buffer is used differently, depending on whether we are scattering + // the vertical scanlines, or gathering them. + // If scattering, it's used at the temp buffer to accumulate each output. + // If gathering, it's just the output buffer. + vertical_buffer_size = horizontal->scale_info.output_sub_size * effective_channels * sizeof(float) + sizeof(float); // extra float for padding + + // we make two passes through this loop, 1st to add everything up, 2nd to allocate and init + for(;;) + { + int i; + void * advance_mem = alloced; + int copy_horizontal = 0; + stbir__sampler * possibly_use_horizontal_for_pivot = 0; + +#ifdef STBIR__SEPARATE_ALLOCATIONS + #define STBIR__NEXT_PTR( ptr, size, ntype ) if ( alloced ) { void * p = STBIR_MALLOC( size, user_data); if ( p == 0 ) { stbir__free_internal_mem( info ); return 0; } (ptr) = (ntype*)p; } +#else + #define STBIR__NEXT_PTR( ptr, size, ntype ) advance_mem = (void*) ( ( ((size_t)advance_mem) + 15 ) & ~15 ); if ( alloced ) ptr = (ntype*)advance_mem; advance_mem = ((char*)advance_mem) + (size); +#endif + + STBIR__NEXT_PTR( info, sizeof( stbir__info ), stbir__info ); + + STBIR__NEXT_PTR( info->split_info, sizeof( stbir__per_split_info ) * splits, stbir__per_split_info ); + + if ( info ) + { + static stbir__alpha_weight_func * fancy_alpha_weights[6] = { stbir__fancy_alpha_weight_4ch, stbir__fancy_alpha_weight_4ch, stbir__fancy_alpha_weight_4ch, stbir__fancy_alpha_weight_4ch, stbir__fancy_alpha_weight_2ch, stbir__fancy_alpha_weight_2ch }; + static stbir__alpha_unweight_func * fancy_alpha_unweights[6] = { stbir__fancy_alpha_unweight_4ch, stbir__fancy_alpha_unweight_4ch, stbir__fancy_alpha_unweight_4ch, stbir__fancy_alpha_unweight_4ch, stbir__fancy_alpha_unweight_2ch, stbir__fancy_alpha_unweight_2ch }; + static stbir__alpha_weight_func * simple_alpha_weights[6] = { stbir__simple_alpha_weight_4ch, stbir__simple_alpha_weight_4ch, stbir__simple_alpha_weight_4ch, stbir__simple_alpha_weight_4ch, stbir__simple_alpha_weight_2ch, stbir__simple_alpha_weight_2ch }; + static stbir__alpha_unweight_func * simple_alpha_unweights[6] = { stbir__simple_alpha_unweight_4ch, stbir__simple_alpha_unweight_4ch, stbir__simple_alpha_unweight_4ch, stbir__simple_alpha_unweight_4ch, stbir__simple_alpha_unweight_2ch, stbir__simple_alpha_unweight_2ch }; + + // initialize info fields + info->alloced_mem = alloced; + info->alloced_total = alloced_total; + + info->channels = channels; + info->effective_channels = effective_channels; + + info->offset_x = new_x; + info->offset_y = new_y; + info->alloc_ring_buffer_num_entries = alloc_ring_buffer_num_entries; + info->ring_buffer_num_entries = 0; + info->ring_buffer_length_bytes = ring_buffer_length_bytes; + info->splits = splits; + info->vertical_first = vertical_first; + + info->input_pixel_layout_internal = input_pixel_layout; + info->output_pixel_layout_internal = output_pixel_layout; + + // setup alpha weight functions + info->alpha_weight = 0; + info->alpha_unweight = 0; + + // handle alpha weighting functions and overrides + if ( alpha_weighting_type == 2 ) + { + // high quality alpha multiplying on the way in, dividing on the way out + info->alpha_weight = fancy_alpha_weights[ input_pixel_layout - STBIRI_RGBA ]; + info->alpha_unweight = fancy_alpha_unweights[ output_pixel_layout - STBIRI_RGBA ]; + } + else if ( alpha_weighting_type == 4 ) + { + // fast alpha multiplying on the way in, dividing on the way out + info->alpha_weight = simple_alpha_weights[ input_pixel_layout - STBIRI_RGBA ]; + info->alpha_unweight = simple_alpha_unweights[ output_pixel_layout - STBIRI_RGBA ]; + } + else if ( alpha_weighting_type == 1 ) + { + // fast alpha on the way in, leave in premultiplied form on way out + info->alpha_weight = simple_alpha_weights[ input_pixel_layout - STBIRI_RGBA ]; + } + else if ( alpha_weighting_type == 3 ) + { + // incoming is premultiplied, fast alpha dividing on the way out - non-premultiplied output + info->alpha_unweight = simple_alpha_unweights[ output_pixel_layout - STBIRI_RGBA ]; + } + + // handle 3-chan color flipping, using the alpha weight path + if ( ( ( input_pixel_layout == STBIRI_RGB ) && ( output_pixel_layout == STBIRI_BGR ) ) || + ( ( input_pixel_layout == STBIRI_BGR ) && ( output_pixel_layout == STBIRI_RGB ) ) ) + { + // do the flipping on the smaller of the two ends + if ( horizontal->scale_info.scale < 1.0f ) + info->alpha_unweight = stbir__simple_flip_3ch; + else + info->alpha_weight = stbir__simple_flip_3ch; + } + + } + + // get all the per-split buffers + for( i = 0 ; i < splits ; i++ ) + { + STBIR__NEXT_PTR( info->split_info[i].decode_buffer, decode_buffer_size, float ); + +#ifdef STBIR__SEPARATE_ALLOCATIONS + + #ifdef STBIR_SIMD8 + if ( ( info ) && ( effective_channels == 3 ) ) + ++info->split_info[i].decode_buffer; // avx in 3 channel mode needs one float at the start of the buffer + #endif + + STBIR__NEXT_PTR( info->split_info[i].ring_buffers, alloc_ring_buffer_num_entries * sizeof(float*), float* ); + { + int j; + for( j = 0 ; j < alloc_ring_buffer_num_entries ; j++ ) + { + STBIR__NEXT_PTR( info->split_info[i].ring_buffers[j], ring_buffer_length_bytes, float ); + #ifdef STBIR_SIMD8 + if ( ( info ) && ( effective_channels == 3 ) ) + ++info->split_info[i].ring_buffers[j]; // avx in 3 channel mode needs one float at the start of the buffer + #endif + } + } +#else + STBIR__NEXT_PTR( info->split_info[i].ring_buffer, ring_buffer_size, float ); +#endif + STBIR__NEXT_PTR( info->split_info[i].vertical_buffer, vertical_buffer_size, float ); + } + + // alloc memory for to-be-pivoted coeffs (if necessary) + if ( vertical->is_gather == 0 ) + { + int both; + int temp_mem_amt; + + // when in vertical scatter mode, we first build the coefficients in gather mode, and then pivot after, + // that means we need two buffers, so we try to use the decode buffer and ring buffer for this. if that + // is too small, we just allocate extra memory to use as this temp. + + both = vertical->gather_prescatter_contributors_size + vertical->gather_prescatter_coefficients_size; + +#ifdef STBIR__SEPARATE_ALLOCATIONS + temp_mem_amt = decode_buffer_size; +#else + temp_mem_amt = ( decode_buffer_size + ring_buffer_size + vertical_buffer_size ) * splits; +#endif + if ( temp_mem_amt >= both ) + { + if ( info ) + { + vertical->gather_prescatter_contributors = (stbir__contributors*)info->split_info[0].decode_buffer; + vertical->gather_prescatter_coefficients = (float*) ( ( (char*)info->split_info[0].decode_buffer ) + vertical->gather_prescatter_contributors_size ); + } + } + else + { + // ring+decode memory is too small, so allocate temp memory + STBIR__NEXT_PTR( vertical->gather_prescatter_contributors, vertical->gather_prescatter_contributors_size, stbir__contributors ); + STBIR__NEXT_PTR( vertical->gather_prescatter_coefficients, vertical->gather_prescatter_coefficients_size, float ); + } + } + + STBIR__NEXT_PTR( horizontal->contributors, horizontal->contributors_size, stbir__contributors ); + STBIR__NEXT_PTR( horizontal->coefficients, horizontal->coefficients_size, float ); + + // are the two filters identical?? (happens a lot with mipmap generation) + if ( ( horizontal->filter_kernel == vertical->filter_kernel ) && ( horizontal->filter_support == vertical->filter_support ) && ( horizontal->edge == vertical->edge ) && ( horizontal->scale_info.output_sub_size == vertical->scale_info.output_sub_size ) ) + { + float diff_scale = horizontal->scale_info.scale - vertical->scale_info.scale; + float diff_shift = horizontal->scale_info.pixel_shift - vertical->scale_info.pixel_shift; + if ( diff_scale < 0.0f ) diff_scale = -diff_scale; + if ( diff_shift < 0.0f ) diff_shift = -diff_shift; + if ( ( diff_scale <= stbir__small_float ) && ( diff_shift <= stbir__small_float ) ) + { + if ( horizontal->is_gather == vertical->is_gather ) + { + copy_horizontal = 1; + goto no_vert_alloc; + } + // everything matches, but vertical is scatter, horizontal is gather, use horizontal coeffs for vertical pivot coeffs + possibly_use_horizontal_for_pivot = horizontal; + } + } + + STBIR__NEXT_PTR( vertical->contributors, vertical->contributors_size, stbir__contributors ); + STBIR__NEXT_PTR( vertical->coefficients, vertical->coefficients_size, float ); + + no_vert_alloc: + + if ( info ) + { + STBIR_PROFILE_BUILD_START( horizontal ); + + stbir__calculate_filters( horizontal, 0, user_data STBIR_ONLY_PROFILE_BUILD_SET_INFO ); + + // setup the horizontal gather functions + // start with defaulting to the n_coeffs functions (specialized on channels and remnant leftover) + info->horizontal_gather_channels = stbir__horizontal_gather_n_coeffs_funcs[ effective_channels ][ horizontal->extent_info.widest & 3 ]; + // but if the number of coeffs <= 12, use another set of special cases. <=12 coeffs is any enlarging resize, or shrinking resize down to about 1/3 size + if ( horizontal->extent_info.widest <= 12 ) + info->horizontal_gather_channels = stbir__horizontal_gather_channels_funcs[ effective_channels ][ horizontal->extent_info.widest - 1 ]; + + info->scanline_extents.conservative.n0 = conservative->n0; + info->scanline_extents.conservative.n1 = conservative->n1; + + // get exact extents + stbir__get_extents( horizontal, &info->scanline_extents ); + + // pack the horizontal coeffs + horizontal->coefficient_width = stbir__pack_coefficients(horizontal->num_contributors, horizontal->contributors, horizontal->coefficients, horizontal->coefficient_width, horizontal->extent_info.widest, info->scanline_extents.conservative.n0, info->scanline_extents.conservative.n1 ); + + STBIR_MEMCPY( &info->horizontal, horizontal, sizeof( stbir__sampler ) ); + + STBIR_PROFILE_BUILD_END( horizontal ); + + if ( copy_horizontal ) + { + STBIR_MEMCPY( &info->vertical, horizontal, sizeof( stbir__sampler ) ); + } + else + { + STBIR_PROFILE_BUILD_START( vertical ); + + stbir__calculate_filters( vertical, possibly_use_horizontal_for_pivot, user_data STBIR_ONLY_PROFILE_BUILD_SET_INFO ); + STBIR_MEMCPY( &info->vertical, vertical, sizeof( stbir__sampler ) ); + + STBIR_PROFILE_BUILD_END( vertical ); + } + + // setup the vertical split ranges + stbir__get_split_info( info->split_info, info->splits, info->vertical.scale_info.output_sub_size, info->vertical.filter_pixel_margin, info->vertical.scale_info.input_full_size ); + + // now we know precisely how many entries we need + info->ring_buffer_num_entries = info->vertical.extent_info.widest; + + // we never need more ring buffer entries than the scanlines we're outputting + if ( ( !info->vertical.is_gather ) && ( info->ring_buffer_num_entries > conservative_split_output_size ) ) + info->ring_buffer_num_entries = conservative_split_output_size; + STBIR_ASSERT( info->ring_buffer_num_entries <= info->alloc_ring_buffer_num_entries ); + + // a few of the horizontal gather functions read one dword past the end (but mask it out), so put in a normal value so no snans or denormals accidentally sneak in + for( i = 0 ; i < splits ; i++ ) + { + int width, ofs; + + // find the right most span + if ( info->scanline_extents.spans[0].n1 > info->scanline_extents.spans[1].n1 ) + width = info->scanline_extents.spans[0].n1 - info->scanline_extents.spans[0].n0; + else + width = info->scanline_extents.spans[1].n1 - info->scanline_extents.spans[1].n0; + + // this calc finds the exact end of the decoded scanline for all filter modes. + // usually this is just the width * effective channels. But we have to account + // for the area to the left of the scanline for wrap filtering and alignment, this + // is stored as a negative value in info->scanline_extents.conservative.n0. Next, + // we need to skip the exact size of the right hand size filter area (again for + // wrap mode), this is in info->scanline_extents.edge_sizes[1]). + ofs = ( width + 1 - info->scanline_extents.conservative.n0 + info->scanline_extents.edge_sizes[1] ) * effective_channels; + + // place a known, but numerically valid value in the decode buffer + info->split_info[i].decode_buffer[ ofs ] = 9999.0f; + + // if vertical filtering first, place a known, but numerically valid value in the all + // of the ring buffer accumulators + if ( vertical_first ) + { + int j; + for( j = 0; j < info->ring_buffer_num_entries ; j++ ) + { + stbir__get_ring_buffer_entry( info, info->split_info + i, j )[ ofs ] = 9999.0f; + } + } + } + } + + #undef STBIR__NEXT_PTR + + + // is this the first time through loop? + if ( info == 0 ) + { + alloced_total = (int) ( 15 + (size_t)advance_mem ); + alloced = STBIR_MALLOC( alloced_total, user_data ); + if ( alloced == 0 ) + return 0; + } + else + return info; // success + } +} + +static int stbir__perform_resize( stbir__info const * info, int split_start, int split_count ) +{ + stbir__per_split_info * split_info = info->split_info + split_start; + + STBIR_PROFILE_CLEAR_EXTRAS(); + + STBIR_PROFILE_FIRST_START( looping ); + if (info->vertical.is_gather) + stbir__vertical_gather_loop( info, split_info, split_count ); + else + stbir__vertical_scatter_loop( info, split_info, split_count ); + STBIR_PROFILE_END( looping ); + + return 1; +} + +static void stbir__update_info_from_resize( stbir__info * info, STBIR_RESIZE * resize ) +{ + static stbir__decode_pixels_func * decode_simple[STBIR_TYPE_HALF_FLOAT-STBIR_TYPE_UINT8_SRGB+1]= + { + /* 1ch-4ch */ stbir__decode_uint8_srgb, stbir__decode_uint8_srgb, 0, stbir__decode_float_linear, stbir__decode_half_float_linear, + }; + + static stbir__decode_pixels_func * decode_alphas[STBIRI_AR-STBIRI_RGBA+1][STBIR_TYPE_HALF_FLOAT-STBIR_TYPE_UINT8_SRGB+1]= + { + { /* RGBA */ stbir__decode_uint8_srgb4_linearalpha, stbir__decode_uint8_srgb, 0, stbir__decode_float_linear, stbir__decode_half_float_linear }, + { /* BGRA */ stbir__decode_uint8_srgb4_linearalpha_BGRA, stbir__decode_uint8_srgb_BGRA, 0, stbir__decode_float_linear_BGRA, stbir__decode_half_float_linear_BGRA }, + { /* ARGB */ stbir__decode_uint8_srgb4_linearalpha_ARGB, stbir__decode_uint8_srgb_ARGB, 0, stbir__decode_float_linear_ARGB, stbir__decode_half_float_linear_ARGB }, + { /* ABGR */ stbir__decode_uint8_srgb4_linearalpha_ABGR, stbir__decode_uint8_srgb_ABGR, 0, stbir__decode_float_linear_ABGR, stbir__decode_half_float_linear_ABGR }, + { /* RA */ stbir__decode_uint8_srgb2_linearalpha, stbir__decode_uint8_srgb, 0, stbir__decode_float_linear, stbir__decode_half_float_linear }, + { /* AR */ stbir__decode_uint8_srgb2_linearalpha_AR, stbir__decode_uint8_srgb_AR, 0, stbir__decode_float_linear_AR, stbir__decode_half_float_linear_AR }, + }; + + static stbir__decode_pixels_func * decode_simple_scaled_or_not[2][2]= + { + { stbir__decode_uint8_linear_scaled, stbir__decode_uint8_linear }, { stbir__decode_uint16_linear_scaled, stbir__decode_uint16_linear }, + }; + + static stbir__decode_pixels_func * decode_alphas_scaled_or_not[STBIRI_AR-STBIRI_RGBA+1][2][2]= + { + { /* RGBA */ { stbir__decode_uint8_linear_scaled, stbir__decode_uint8_linear }, { stbir__decode_uint16_linear_scaled, stbir__decode_uint16_linear } }, + { /* BGRA */ { stbir__decode_uint8_linear_scaled_BGRA, stbir__decode_uint8_linear_BGRA }, { stbir__decode_uint16_linear_scaled_BGRA, stbir__decode_uint16_linear_BGRA } }, + { /* ARGB */ { stbir__decode_uint8_linear_scaled_ARGB, stbir__decode_uint8_linear_ARGB }, { stbir__decode_uint16_linear_scaled_ARGB, stbir__decode_uint16_linear_ARGB } }, + { /* ABGR */ { stbir__decode_uint8_linear_scaled_ABGR, stbir__decode_uint8_linear_ABGR }, { stbir__decode_uint16_linear_scaled_ABGR, stbir__decode_uint16_linear_ABGR } }, + { /* RA */ { stbir__decode_uint8_linear_scaled, stbir__decode_uint8_linear }, { stbir__decode_uint16_linear_scaled, stbir__decode_uint16_linear } }, + { /* AR */ { stbir__decode_uint8_linear_scaled_AR, stbir__decode_uint8_linear_AR }, { stbir__decode_uint16_linear_scaled_AR, stbir__decode_uint16_linear_AR } } + }; + + static stbir__encode_pixels_func * encode_simple[STBIR_TYPE_HALF_FLOAT-STBIR_TYPE_UINT8_SRGB+1]= + { + /* 1ch-4ch */ stbir__encode_uint8_srgb, stbir__encode_uint8_srgb, 0, stbir__encode_float_linear, stbir__encode_half_float_linear, + }; + + static stbir__encode_pixels_func * encode_alphas[STBIRI_AR-STBIRI_RGBA+1][STBIR_TYPE_HALF_FLOAT-STBIR_TYPE_UINT8_SRGB+1]= + { + { /* RGBA */ stbir__encode_uint8_srgb4_linearalpha, stbir__encode_uint8_srgb, 0, stbir__encode_float_linear, stbir__encode_half_float_linear }, + { /* BGRA */ stbir__encode_uint8_srgb4_linearalpha_BGRA, stbir__encode_uint8_srgb_BGRA, 0, stbir__encode_float_linear_BGRA, stbir__encode_half_float_linear_BGRA }, + { /* ARGB */ stbir__encode_uint8_srgb4_linearalpha_ARGB, stbir__encode_uint8_srgb_ARGB, 0, stbir__encode_float_linear_ARGB, stbir__encode_half_float_linear_ARGB }, + { /* ABGR */ stbir__encode_uint8_srgb4_linearalpha_ABGR, stbir__encode_uint8_srgb_ABGR, 0, stbir__encode_float_linear_ABGR, stbir__encode_half_float_linear_ABGR }, + { /* RA */ stbir__encode_uint8_srgb2_linearalpha, stbir__encode_uint8_srgb, 0, stbir__encode_float_linear, stbir__encode_half_float_linear }, + { /* AR */ stbir__encode_uint8_srgb2_linearalpha_AR, stbir__encode_uint8_srgb_AR, 0, stbir__encode_float_linear_AR, stbir__encode_half_float_linear_AR } + }; + + static stbir__encode_pixels_func * encode_simple_scaled_or_not[2][2]= + { + { stbir__encode_uint8_linear_scaled, stbir__encode_uint8_linear }, { stbir__encode_uint16_linear_scaled, stbir__encode_uint16_linear }, + }; + + static stbir__encode_pixels_func * encode_alphas_scaled_or_not[STBIRI_AR-STBIRI_RGBA+1][2][2]= + { + { /* RGBA */ { stbir__encode_uint8_linear_scaled, stbir__encode_uint8_linear }, { stbir__encode_uint16_linear_scaled, stbir__encode_uint16_linear } }, + { /* BGRA */ { stbir__encode_uint8_linear_scaled_BGRA, stbir__encode_uint8_linear_BGRA }, { stbir__encode_uint16_linear_scaled_BGRA, stbir__encode_uint16_linear_BGRA } }, + { /* ARGB */ { stbir__encode_uint8_linear_scaled_ARGB, stbir__encode_uint8_linear_ARGB }, { stbir__encode_uint16_linear_scaled_ARGB, stbir__encode_uint16_linear_ARGB } }, + { /* ABGR */ { stbir__encode_uint8_linear_scaled_ABGR, stbir__encode_uint8_linear_ABGR }, { stbir__encode_uint16_linear_scaled_ABGR, stbir__encode_uint16_linear_ABGR } }, + { /* RA */ { stbir__encode_uint8_linear_scaled, stbir__encode_uint8_linear }, { stbir__encode_uint16_linear_scaled, stbir__encode_uint16_linear } }, + { /* AR */ { stbir__encode_uint8_linear_scaled_AR, stbir__encode_uint8_linear_AR }, { stbir__encode_uint16_linear_scaled_AR, stbir__encode_uint16_linear_AR } } + }; + + stbir__decode_pixels_func * decode_pixels = 0; + stbir__encode_pixels_func * encode_pixels = 0; + stbir_datatype input_type, output_type; + + input_type = resize->input_data_type; + output_type = resize->output_data_type; + info->input_data = resize->input_pixels; + info->input_stride_bytes = resize->input_stride_in_bytes; + info->output_stride_bytes = resize->output_stride_in_bytes; + + // if we're completely point sampling, then we can turn off SRGB + if ( ( info->horizontal.filter_enum == STBIR_FILTER_POINT_SAMPLE ) && ( info->vertical.filter_enum == STBIR_FILTER_POINT_SAMPLE ) ) + { + if ( ( ( input_type == STBIR_TYPE_UINT8_SRGB ) || ( input_type == STBIR_TYPE_UINT8_SRGB_ALPHA ) ) && + ( ( output_type == STBIR_TYPE_UINT8_SRGB ) || ( output_type == STBIR_TYPE_UINT8_SRGB_ALPHA ) ) ) + { + input_type = STBIR_TYPE_UINT8; + output_type = STBIR_TYPE_UINT8; + } + } + + // recalc the output and input strides + if ( info->input_stride_bytes == 0 ) + info->input_stride_bytes = info->channels * info->horizontal.scale_info.input_full_size * stbir__type_size[input_type]; + + if ( info->output_stride_bytes == 0 ) + info->output_stride_bytes = info->channels * info->horizontal.scale_info.output_sub_size * stbir__type_size[output_type]; + + // calc offset + info->output_data = ( (char*) resize->output_pixels ) + ( (ptrdiff_t) info->offset_y * (ptrdiff_t) resize->output_stride_in_bytes ) + ( info->offset_x * info->channels * stbir__type_size[output_type] ); + + info->in_pixels_cb = resize->input_cb; + info->user_data = resize->user_data; + info->out_pixels_cb = resize->output_cb; + + // setup the input format converters + if ( ( input_type == STBIR_TYPE_UINT8 ) || ( input_type == STBIR_TYPE_UINT16 ) ) + { + int non_scaled = 0; + + // check if we can run unscaled - 0-255.0/0-65535.0 instead of 0-1.0 (which is a tiny bit faster when doing linear 8->8 or 16->16) + if ( ( !info->alpha_weight ) && ( !info->alpha_unweight ) ) // don't short circuit when alpha weighting (get everything to 0-1.0 as usual) + if ( ( ( input_type == STBIR_TYPE_UINT8 ) && ( output_type == STBIR_TYPE_UINT8 ) ) || ( ( input_type == STBIR_TYPE_UINT16 ) && ( output_type == STBIR_TYPE_UINT16 ) ) ) + non_scaled = 1; + + if ( info->input_pixel_layout_internal <= STBIRI_4CHANNEL ) + decode_pixels = decode_simple_scaled_or_not[ input_type == STBIR_TYPE_UINT16 ][ non_scaled ]; + else + decode_pixels = decode_alphas_scaled_or_not[ ( info->input_pixel_layout_internal - STBIRI_RGBA ) % ( STBIRI_AR-STBIRI_RGBA+1 ) ][ input_type == STBIR_TYPE_UINT16 ][ non_scaled ]; + } + else + { + if ( info->input_pixel_layout_internal <= STBIRI_4CHANNEL ) + decode_pixels = decode_simple[ input_type - STBIR_TYPE_UINT8_SRGB ]; + else + decode_pixels = decode_alphas[ ( info->input_pixel_layout_internal - STBIRI_RGBA ) % ( STBIRI_AR-STBIRI_RGBA+1 ) ][ input_type - STBIR_TYPE_UINT8_SRGB ]; + } + + // setup the output format converters + if ( ( output_type == STBIR_TYPE_UINT8 ) || ( output_type == STBIR_TYPE_UINT16 ) ) + { + int non_scaled = 0; + + // check if we can run unscaled - 0-255.0/0-65535.0 instead of 0-1.0 (which is a tiny bit faster when doing linear 8->8 or 16->16) + if ( ( !info->alpha_weight ) && ( !info->alpha_unweight ) ) // don't short circuit when alpha weighting (get everything to 0-1.0 as usual) + if ( ( ( input_type == STBIR_TYPE_UINT8 ) && ( output_type == STBIR_TYPE_UINT8 ) ) || ( ( input_type == STBIR_TYPE_UINT16 ) && ( output_type == STBIR_TYPE_UINT16 ) ) ) + non_scaled = 1; + + if ( info->output_pixel_layout_internal <= STBIRI_4CHANNEL ) + encode_pixels = encode_simple_scaled_or_not[ output_type == STBIR_TYPE_UINT16 ][ non_scaled ]; + else + encode_pixels = encode_alphas_scaled_or_not[ ( info->output_pixel_layout_internal - STBIRI_RGBA ) % ( STBIRI_AR-STBIRI_RGBA+1 ) ][ output_type == STBIR_TYPE_UINT16 ][ non_scaled ]; + } + else + { + if ( info->output_pixel_layout_internal <= STBIRI_4CHANNEL ) + encode_pixels = encode_simple[ output_type - STBIR_TYPE_UINT8_SRGB ]; + else + encode_pixels = encode_alphas[ ( info->output_pixel_layout_internal - STBIRI_RGBA ) % ( STBIRI_AR-STBIRI_RGBA+1 ) ][ output_type - STBIR_TYPE_UINT8_SRGB ]; + } + + info->input_type = input_type; + info->output_type = output_type; + info->decode_pixels = decode_pixels; + info->encode_pixels = encode_pixels; +} + +static void stbir__clip( int * outx, int * outsubw, int outw, double * u0, double * u1 ) +{ + double per, adj; + int over; + + // do left/top edge + if ( *outx < 0 ) + { + per = ( (double)*outx ) / ( (double)*outsubw ); // is negative + adj = per * ( *u1 - *u0 ); + *u0 -= adj; // increases u0 + *outx = 0; + } + + // do right/bot edge + over = outw - ( *outx + *outsubw ); + if ( over < 0 ) + { + per = ( (double)over ) / ( (double)*outsubw ); // is negative + adj = per * ( *u1 - *u0 ); + *u1 += adj; // decrease u1 + *outsubw = outw - *outx; + } +} + +// converts a double to a rational that has less than one float bit of error (returns 0 if unable to do so) +static int stbir__double_to_rational(double f, stbir_uint32 limit, stbir_uint32 *numer, stbir_uint32 *denom, int limit_denom ) // limit_denom (1) or limit numer (0) +{ + double err; + stbir_uint64 top, bot; + stbir_uint64 numer_last = 0; + stbir_uint64 denom_last = 1; + stbir_uint64 numer_estimate = 1; + stbir_uint64 denom_estimate = 0; + + // scale to past float error range + top = (stbir_uint64)( f * (double)(1 << 25) ); + bot = 1 << 25; + + // keep refining, but usually stops in a few loops - usually 5 for bad cases + for(;;) + { + stbir_uint64 est, temp; + + // hit limit, break out and do best full range estimate + if ( ( ( limit_denom ) ? denom_estimate : numer_estimate ) >= limit ) + break; + + // is the current error less than 1 bit of a float? if so, we're done + if ( denom_estimate ) + { + err = ( (double)numer_estimate / (double)denom_estimate ) - f; + if ( err < 0.0 ) err = -err; + if ( err < ( 1.0 / (double)(1<<24) ) ) + { + // yup, found it + *numer = (stbir_uint32) numer_estimate; + *denom = (stbir_uint32) denom_estimate; + return 1; + } + } + + // no more refinement bits left? break out and do full range estimate + if ( bot == 0 ) + break; + + // gcd the estimate bits + est = top / bot; + temp = top % bot; + top = bot; + bot = temp; + + // move remainders + temp = est * denom_estimate + denom_last; + denom_last = denom_estimate; + denom_estimate = temp; + + // move remainders + temp = est * numer_estimate + numer_last; + numer_last = numer_estimate; + numer_estimate = temp; + } + + // we didn't fine anything good enough for float, use a full range estimate + if ( limit_denom ) + { + numer_estimate= (stbir_uint64)( f * (double)limit + 0.5 ); + denom_estimate = limit; + } + else + { + numer_estimate = limit; + denom_estimate = (stbir_uint64)( ( (double)limit / f ) + 0.5 ); + } + + *numer = (stbir_uint32) numer_estimate; + *denom = (stbir_uint32) denom_estimate; + + err = ( denom_estimate ) ? ( ( (double)(stbir_uint32)numer_estimate / (double)(stbir_uint32)denom_estimate ) - f ) : 1.0; + if ( err < 0.0 ) err = -err; + return ( err < ( 1.0 / (double)(1<<24) ) ) ? 1 : 0; +} + +static int stbir__calculate_region_transform( stbir__scale_info * scale_info, int output_full_range, int * output_offset, int output_sub_range, int input_full_range, double input_s0, double input_s1 ) +{ + double output_range, input_range, output_s, input_s, ratio, scale; + + input_s = input_s1 - input_s0; + + // null area + if ( ( output_full_range == 0 ) || ( input_full_range == 0 ) || + ( output_sub_range == 0 ) || ( input_s <= stbir__small_float ) ) + return 0; + + // are either of the ranges completely out of bounds? + if ( ( *output_offset >= output_full_range ) || ( ( *output_offset + output_sub_range ) <= 0 ) || ( input_s0 >= (1.0f-stbir__small_float) ) || ( input_s1 <= stbir__small_float ) ) + return 0; + + output_range = (double)output_full_range; + input_range = (double)input_full_range; + + output_s = ( (double)output_sub_range) / output_range; + + // figure out the scaling to use + ratio = output_s / input_s; + + // save scale before clipping + scale = ( output_range / input_range ) * ratio; + scale_info->scale = (float)scale; + scale_info->inv_scale = (float)( 1.0 / scale ); + + // clip output area to left/right output edges (and adjust input area) + stbir__clip( output_offset, &output_sub_range, output_full_range, &input_s0, &input_s1 ); + + // recalc input area + input_s = input_s1 - input_s0; + + // after clipping do we have zero input area? + if ( input_s <= stbir__small_float ) + return 0; + + // calculate and store the starting source offsets in output pixel space + scale_info->pixel_shift = (float) ( input_s0 * ratio * output_range ); + + scale_info->scale_is_rational = stbir__double_to_rational( scale, ( scale <= 1.0 ) ? output_full_range : input_full_range, &scale_info->scale_numerator, &scale_info->scale_denominator, ( scale >= 1.0 ) ); + + scale_info->input_full_size = input_full_range; + scale_info->output_sub_size = output_sub_range; + + return 1; +} + + +static void stbir__init_and_set_layout( STBIR_RESIZE * resize, stbir_pixel_layout pixel_layout, stbir_datatype data_type ) +{ + resize->input_cb = 0; + resize->output_cb = 0; + resize->user_data = resize; + resize->samplers = 0; + resize->called_alloc = 0; + resize->horizontal_filter = STBIR_FILTER_DEFAULT; + resize->horizontal_filter_kernel = 0; resize->horizontal_filter_support = 0; + resize->vertical_filter = STBIR_FILTER_DEFAULT; + resize->vertical_filter_kernel = 0; resize->vertical_filter_support = 0; + resize->horizontal_edge = STBIR_EDGE_CLAMP; + resize->vertical_edge = STBIR_EDGE_CLAMP; + resize->input_s0 = 0; resize->input_t0 = 0; resize->input_s1 = 1; resize->input_t1 = 1; + resize->output_subx = 0; resize->output_suby = 0; resize->output_subw = resize->output_w; resize->output_subh = resize->output_h; + resize->input_data_type = data_type; + resize->output_data_type = data_type; + resize->input_pixel_layout_public = pixel_layout; + resize->output_pixel_layout_public = pixel_layout; + resize->needs_rebuild = 1; +} + +STBIRDEF void stbir_resize_init( STBIR_RESIZE * resize, + const void *input_pixels, int input_w, int input_h, int input_stride_in_bytes, // stride can be zero + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, // stride can be zero + stbir_pixel_layout pixel_layout, stbir_datatype data_type ) +{ + resize->input_pixels = input_pixels; + resize->input_w = input_w; + resize->input_h = input_h; + resize->input_stride_in_bytes = input_stride_in_bytes; + resize->output_pixels = output_pixels; + resize->output_w = output_w; + resize->output_h = output_h; + resize->output_stride_in_bytes = output_stride_in_bytes; + resize->fast_alpha = 0; + + stbir__init_and_set_layout( resize, pixel_layout, data_type ); +} + +// You can update parameters any time after resize_init +STBIRDEF void stbir_set_datatypes( STBIR_RESIZE * resize, stbir_datatype input_type, stbir_datatype output_type ) // by default, datatype from resize_init +{ + resize->input_data_type = input_type; + resize->output_data_type = output_type; + if ( ( resize->samplers ) && ( !resize->needs_rebuild ) ) + stbir__update_info_from_resize( resize->samplers, resize ); +} + +STBIRDEF void stbir_set_pixel_callbacks( STBIR_RESIZE * resize, stbir_input_callback * input_cb, stbir_output_callback * output_cb ) // no callbacks by default +{ + resize->input_cb = input_cb; + resize->output_cb = output_cb; + + if ( ( resize->samplers ) && ( !resize->needs_rebuild ) ) + { + resize->samplers->in_pixels_cb = input_cb; + resize->samplers->out_pixels_cb = output_cb; + } +} + +STBIRDEF void stbir_set_user_data( STBIR_RESIZE * resize, void * user_data ) // pass back STBIR_RESIZE* by default +{ + resize->user_data = user_data; + if ( ( resize->samplers ) && ( !resize->needs_rebuild ) ) + resize->samplers->user_data = user_data; +} + +STBIRDEF void stbir_set_buffer_ptrs( STBIR_RESIZE * resize, const void * input_pixels, int input_stride_in_bytes, void * output_pixels, int output_stride_in_bytes ) +{ + resize->input_pixels = input_pixels; + resize->input_stride_in_bytes = input_stride_in_bytes; + resize->output_pixels = output_pixels; + resize->output_stride_in_bytes = output_stride_in_bytes; + if ( ( resize->samplers ) && ( !resize->needs_rebuild ) ) + stbir__update_info_from_resize( resize->samplers, resize ); +} + + +STBIRDEF int stbir_set_edgemodes( STBIR_RESIZE * resize, stbir_edge horizontal_edge, stbir_edge vertical_edge ) // CLAMP by default +{ + resize->horizontal_edge = horizontal_edge; + resize->vertical_edge = vertical_edge; + resize->needs_rebuild = 1; + return 1; +} + +STBIRDEF int stbir_set_filters( STBIR_RESIZE * resize, stbir_filter horizontal_filter, stbir_filter vertical_filter ) // STBIR_DEFAULT_FILTER_UPSAMPLE/DOWNSAMPLE by default +{ + resize->horizontal_filter = horizontal_filter; + resize->vertical_filter = vertical_filter; + resize->needs_rebuild = 1; + return 1; +} + +STBIRDEF int stbir_set_filter_callbacks( STBIR_RESIZE * resize, stbir__kernel_callback * horizontal_filter, stbir__support_callback * horizontal_support, stbir__kernel_callback * vertical_filter, stbir__support_callback * vertical_support ) +{ + resize->horizontal_filter_kernel = horizontal_filter; resize->horizontal_filter_support = horizontal_support; + resize->vertical_filter_kernel = vertical_filter; resize->vertical_filter_support = vertical_support; + resize->needs_rebuild = 1; + return 1; +} + +STBIRDEF int stbir_set_pixel_layouts( STBIR_RESIZE * resize, stbir_pixel_layout input_pixel_layout, stbir_pixel_layout output_pixel_layout ) // sets new pixel layouts +{ + resize->input_pixel_layout_public = input_pixel_layout; + resize->output_pixel_layout_public = output_pixel_layout; + resize->needs_rebuild = 1; + return 1; +} + + +STBIRDEF int stbir_set_non_pm_alpha_speed_over_quality( STBIR_RESIZE * resize, int non_pma_alpha_speed_over_quality ) // sets alpha speed +{ + resize->fast_alpha = non_pma_alpha_speed_over_quality; + resize->needs_rebuild = 1; + return 1; +} + +STBIRDEF int stbir_set_input_subrect( STBIR_RESIZE * resize, double s0, double t0, double s1, double t1 ) // sets input region (full region by default) +{ + resize->input_s0 = s0; + resize->input_t0 = t0; + resize->input_s1 = s1; + resize->input_t1 = t1; + resize->needs_rebuild = 1; + + // are we inbounds? + if ( ( s1 < stbir__small_float ) || ( (s1-s0) < stbir__small_float ) || + ( t1 < stbir__small_float ) || ( (t1-t0) < stbir__small_float ) || + ( s0 > (1.0f-stbir__small_float) ) || + ( t0 > (1.0f-stbir__small_float) ) ) + return 0; + + return 1; +} + +STBIRDEF int stbir_set_output_pixel_subrect( STBIR_RESIZE * resize, int subx, int suby, int subw, int subh ) // sets input region (full region by default) +{ + resize->output_subx = subx; + resize->output_suby = suby; + resize->output_subw = subw; + resize->output_subh = subh; + resize->needs_rebuild = 1; + + // are we inbounds? + if ( ( subx >= resize->output_w ) || ( ( subx + subw ) <= 0 ) || ( suby >= resize->output_h ) || ( ( suby + subh ) <= 0 ) || ( subw == 0 ) || ( subh == 0 ) ) + return 0; + + return 1; +} + +STBIRDEF int stbir_set_pixel_subrect( STBIR_RESIZE * resize, int subx, int suby, int subw, int subh ) // sets both regions (full regions by default) +{ + double s0, t0, s1, t1; + + s0 = ( (double)subx ) / ( (double)resize->output_w ); + t0 = ( (double)suby ) / ( (double)resize->output_h ); + s1 = ( (double)(subx+subw) ) / ( (double)resize->output_w ); + t1 = ( (double)(suby+subh) ) / ( (double)resize->output_h ); + + resize->input_s0 = s0; + resize->input_t0 = t0; + resize->input_s1 = s1; + resize->input_t1 = t1; + resize->output_subx = subx; + resize->output_suby = suby; + resize->output_subw = subw; + resize->output_subh = subh; + resize->needs_rebuild = 1; + + // are we inbounds? + if ( ( subx >= resize->output_w ) || ( ( subx + subw ) <= 0 ) || ( suby >= resize->output_h ) || ( ( suby + subh ) <= 0 ) || ( subw == 0 ) || ( subh == 0 ) ) + return 0; + + return 1; +} + +static int stbir__perform_build( STBIR_RESIZE * resize, int splits ) +{ + stbir__contributors conservative = { 0, 0 }; + stbir__sampler horizontal, vertical; + int new_output_subx, new_output_suby; + stbir__info * out_info; + #ifdef STBIR_PROFILE + stbir__info profile_infod; // used to contain building profile info before everything is allocated + stbir__info * profile_info = &profile_infod; + #endif + + // have we already built the samplers? + if ( resize->samplers ) + return 0; + + #define STBIR_RETURN_ERROR_AND_ASSERT( exp ) STBIR_ASSERT( !(exp) ); if (exp) return 0; + STBIR_RETURN_ERROR_AND_ASSERT( (unsigned)resize->horizontal_filter >= STBIR_FILTER_OTHER) + STBIR_RETURN_ERROR_AND_ASSERT( (unsigned)resize->vertical_filter >= STBIR_FILTER_OTHER) + #undef STBIR_RETURN_ERROR_AND_ASSERT + + if ( splits <= 0 ) + return 0; + + STBIR_PROFILE_BUILD_FIRST_START( build ); + + new_output_subx = resize->output_subx; + new_output_suby = resize->output_suby; + + // do horizontal clip and scale calcs + if ( !stbir__calculate_region_transform( &horizontal.scale_info, resize->output_w, &new_output_subx, resize->output_subw, resize->input_w, resize->input_s0, resize->input_s1 ) ) + return 0; + + // do vertical clip and scale calcs + if ( !stbir__calculate_region_transform( &vertical.scale_info, resize->output_h, &new_output_suby, resize->output_subh, resize->input_h, resize->input_t0, resize->input_t1 ) ) + return 0; + + // if nothing to do, just return + if ( ( horizontal.scale_info.output_sub_size == 0 ) || ( vertical.scale_info.output_sub_size == 0 ) ) + return 0; + + stbir__set_sampler(&horizontal, resize->horizontal_filter, resize->horizontal_filter_kernel, resize->horizontal_filter_support, resize->horizontal_edge, &horizontal.scale_info, 1, resize->user_data ); + stbir__get_conservative_extents( &horizontal, &conservative, resize->user_data ); + stbir__set_sampler(&vertical, resize->vertical_filter, resize->horizontal_filter_kernel, resize->vertical_filter_support, resize->vertical_edge, &vertical.scale_info, 0, resize->user_data ); + + if ( ( vertical.scale_info.output_sub_size / splits ) < STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS ) // each split should be a minimum of 4 scanlines (handwavey choice) + { + splits = vertical.scale_info.output_sub_size / STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS; + if ( splits == 0 ) splits = 1; + } + + STBIR_PROFILE_BUILD_START( alloc ); + out_info = stbir__alloc_internal_mem_and_build_samplers( &horizontal, &vertical, &conservative, resize->input_pixel_layout_public, resize->output_pixel_layout_public, splits, new_output_subx, new_output_suby, resize->fast_alpha, resize->user_data STBIR_ONLY_PROFILE_BUILD_SET_INFO ); + STBIR_PROFILE_BUILD_END( alloc ); + STBIR_PROFILE_BUILD_END( build ); + + if ( out_info ) + { + resize->splits = splits; + resize->samplers = out_info; + resize->needs_rebuild = 0; + #ifdef STBIR_PROFILE + STBIR_MEMCPY( &out_info->profile, &profile_infod.profile, sizeof( out_info->profile ) ); + #endif + + // update anything that can be changed without recalcing samplers + stbir__update_info_from_resize( out_info, resize ); + + return splits; + } + + return 0; +} + +void stbir_free_samplers( STBIR_RESIZE * resize ) +{ + if ( resize->samplers ) + { + stbir__free_internal_mem( resize->samplers ); + resize->samplers = 0; + resize->called_alloc = 0; + } +} + +STBIRDEF int stbir_build_samplers_with_splits( STBIR_RESIZE * resize, int splits ) +{ + if ( ( resize->samplers == 0 ) || ( resize->needs_rebuild ) ) + { + if ( resize->samplers ) + stbir_free_samplers( resize ); + + resize->called_alloc = 1; + return stbir__perform_build( resize, splits ); + } + + STBIR_PROFILE_BUILD_CLEAR( resize->samplers ); + + return 1; +} + +STBIRDEF int stbir_build_samplers( STBIR_RESIZE * resize ) +{ + return stbir_build_samplers_with_splits( resize, 1 ); +} + +STBIRDEF int stbir_resize_extended( STBIR_RESIZE * resize ) +{ + int result; + + if ( ( resize->samplers == 0 ) || ( resize->needs_rebuild ) ) + { + int alloc_state = resize->called_alloc; // remember allocated state + + if ( resize->samplers ) + { + stbir__free_internal_mem( resize->samplers ); + resize->samplers = 0; + } + + if ( !stbir_build_samplers( resize ) ) + return 0; + + resize->called_alloc = alloc_state; + + // if build_samplers succeeded (above), but there are no samplers set, then + // the area to stretch into was zero pixels, so don't do anything and return + // success + if ( resize->samplers == 0 ) + return 1; + } + else + { + // didn't build anything - clear it + STBIR_PROFILE_BUILD_CLEAR( resize->samplers ); + } + + // do resize + result = stbir__perform_resize( resize->samplers, 0, resize->splits ); + + // if we alloced, then free + if ( !resize->called_alloc ) + { + stbir_free_samplers( resize ); + resize->samplers = 0; + } + + return result; +} + +STBIRDEF int stbir_resize_extended_split( STBIR_RESIZE * resize, int split_start, int split_count ) +{ + STBIR_ASSERT( resize->samplers ); + + // if we're just doing the whole thing, call full + if ( ( split_start == -1 ) || ( ( split_start == 0 ) && ( split_count == resize->splits ) ) ) + return stbir_resize_extended( resize ); + + // you **must** build samplers first when using split resize + if ( ( resize->samplers == 0 ) || ( resize->needs_rebuild ) ) + return 0; + + if ( ( split_start >= resize->splits ) || ( split_start < 0 ) || ( ( split_start + split_count ) > resize->splits ) || ( split_count <= 0 ) ) + return 0; + + // do resize + return stbir__perform_resize( resize->samplers, split_start, split_count ); +} + +static int stbir__check_output_stuff( void ** ret_ptr, int * ret_pitch, void * output_pixels, int type_size, int output_w, int output_h, int output_stride_in_bytes, stbir_internal_pixel_layout pixel_layout ) +{ + size_t size; + int pitch; + void * ptr; + + pitch = output_w * type_size * stbir__pixel_channels[ pixel_layout ]; + if ( pitch == 0 ) + return 0; + + if ( output_stride_in_bytes == 0 ) + output_stride_in_bytes = pitch; + + if ( output_stride_in_bytes < pitch ) + return 0; + + size = output_stride_in_bytes * output_h; + if ( size == 0 ) + return 0; + + *ret_ptr = 0; + *ret_pitch = output_stride_in_bytes; + + if ( output_pixels == 0 ) + { + ptr = STBIR_MALLOC( size, 0 ); + if ( ptr == 0 ) + return 0; + + *ret_ptr = ptr; + *ret_pitch = pitch; + } + + return 1; +} + + +STBIRDEF unsigned char * stbir_resize_uint8_linear( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout ) +{ + STBIR_RESIZE resize; + unsigned char * optr; + int opitch; + + if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, sizeof( unsigned char ), output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) + return 0; + + stbir_resize_init( &resize, + input_pixels, input_w, input_h, input_stride_in_bytes, + (optr) ? optr : output_pixels, output_w, output_h, opitch, + pixel_layout, STBIR_TYPE_UINT8 ); + + if ( !stbir_resize_extended( &resize ) ) + { + if ( optr ) + STBIR_FREE( optr, 0 ); + return 0; + } + + return (optr) ? optr : output_pixels; +} + +STBIRDEF unsigned char * stbir_resize_uint8_srgb( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout ) +{ + STBIR_RESIZE resize; + unsigned char * optr; + int opitch; + + if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, sizeof( unsigned char ), output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) + return 0; + + stbir_resize_init( &resize, + input_pixels, input_w, input_h, input_stride_in_bytes, + (optr) ? optr : output_pixels, output_w, output_h, opitch, + pixel_layout, STBIR_TYPE_UINT8_SRGB ); + + if ( !stbir_resize_extended( &resize ) ) + { + if ( optr ) + STBIR_FREE( optr, 0 ); + return 0; + } + + return (optr) ? optr : output_pixels; +} + + +STBIRDEF float * stbir_resize_float_linear( const float *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout ) +{ + STBIR_RESIZE resize; + float * optr; + int opitch; + + if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, sizeof( float ), output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) + return 0; + + stbir_resize_init( &resize, + input_pixels, input_w, input_h, input_stride_in_bytes, + (optr) ? optr : output_pixels, output_w, output_h, opitch, + pixel_layout, STBIR_TYPE_FLOAT ); + + if ( !stbir_resize_extended( &resize ) ) + { + if ( optr ) + STBIR_FREE( optr, 0 ); + return 0; + } + + return (optr) ? optr : output_pixels; +} + + +STBIRDEF void * stbir_resize( const void *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout, stbir_datatype data_type, + stbir_edge edge, stbir_filter filter ) +{ + STBIR_RESIZE resize; + float * optr; + int opitch; + + if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, stbir__type_size[data_type], output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) + return 0; + + stbir_resize_init( &resize, + input_pixels, input_w, input_h, input_stride_in_bytes, + (optr) ? optr : output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout, data_type ); + + resize.horizontal_edge = edge; + resize.vertical_edge = edge; + resize.horizontal_filter = filter; + resize.vertical_filter = filter; + + if ( !stbir_resize_extended( &resize ) ) + { + if ( optr ) + STBIR_FREE( optr, 0 ); + return 0; + } + + return (optr) ? optr : output_pixels; +} + +#ifdef STBIR_PROFILE + +STBIRDEF void stbir_resize_build_profile_info( STBIR_PROFILE_INFO * info, STBIR_RESIZE const * resize ) +{ + static char const * bdescriptions[6] = { "Building", "Allocating", "Horizontal sampler", "Vertical sampler", "Coefficient cleanup", "Coefficient piovot" } ; + stbir__info* samp = resize->samplers; + int i; + + typedef int testa[ (STBIR__ARRAY_SIZE( bdescriptions ) == (STBIR__ARRAY_SIZE( samp->profile.array )-1) )?1:-1]; + typedef int testb[ (sizeof( samp->profile.array ) == (sizeof(samp->profile.named)) )?1:-1]; + typedef int testc[ (sizeof( info->clocks ) >= (sizeof(samp->profile.named)) )?1:-1]; + + for( i = 0 ; i < STBIR__ARRAY_SIZE( bdescriptions ) ; i++) + info->clocks[i] = samp->profile.array[i+1]; + + info->total_clocks = samp->profile.named.total; + info->descriptions = bdescriptions; + info->count = STBIR__ARRAY_SIZE( bdescriptions ); +} + +STBIRDEF void stbir_resize_split_profile_info( STBIR_PROFILE_INFO * info, STBIR_RESIZE const * resize, int split_start, int split_count ) +{ + static char const * descriptions[7] = { "Looping", "Vertical sampling", "Horizontal sampling", "Scanline input", "Scanline output", "Alpha weighting", "Alpha unweighting" }; + stbir__per_split_info * split_info; + int s, i; + + typedef int testa[ (STBIR__ARRAY_SIZE( descriptions ) == (STBIR__ARRAY_SIZE( split_info->profile.array )-1) )?1:-1]; + typedef int testb[ (sizeof( split_info->profile.array ) == (sizeof(split_info->profile.named)) )?1:-1]; + typedef int testc[ (sizeof( info->clocks ) >= (sizeof(split_info->profile.named)) )?1:-1]; + + if ( split_start == -1 ) + { + split_start = 0; + split_count = resize->samplers->splits; + } + + if ( ( split_start >= resize->splits ) || ( split_start < 0 ) || ( ( split_start + split_count ) > resize->splits ) || ( split_count <= 0 ) ) + { + info->total_clocks = 0; + info->descriptions = 0; + info->count = 0; + return; + } + + split_info = resize->samplers->split_info + split_start; + + // sum up the profile from all the splits + for( i = 0 ; i < STBIR__ARRAY_SIZE( descriptions ) ; i++ ) + { + stbir_uint64 sum = 0; + for( s = 0 ; s < split_count ; s++ ) + sum += split_info[s].profile.array[i+1]; + info->clocks[i] = sum; + } + + info->total_clocks = split_info->profile.named.total; + info->descriptions = descriptions; + info->count = STBIR__ARRAY_SIZE( descriptions ); +} + +STBIRDEF void stbir_resize_extended_profile_info( STBIR_PROFILE_INFO * info, STBIR_RESIZE const * resize ) +{ + stbir_resize_split_profile_info( info, resize, -1, 0 ); +} + +#endif // STBIR_PROFILE + +#undef STBIR_BGR +#undef STBIR_1CHANNEL +#undef STBIR_2CHANNEL +#undef STBIR_RGB +#undef STBIR_RGBA +#undef STBIR_4CHANNEL +#undef STBIR_BGRA +#undef STBIR_ARGB +#undef STBIR_ABGR +#undef STBIR_RA +#undef STBIR_AR +#undef STBIR_RGBA_PM +#undef STBIR_BGRA_PM +#undef STBIR_ARGB_PM +#undef STBIR_ABGR_PM +#undef STBIR_RA_PM +#undef STBIR_AR_PM + +#endif // STB_IMAGE_RESIZE_IMPLEMENTATION + +#else // STB_IMAGE_RESIZE_HORIZONTALS&STB_IMAGE_RESIZE_DO_VERTICALS + +// we reinclude the header file to define all the horizontal functions +// specializing each function for the number of coeffs is 20-40% faster *OVERALL* + +// by including the header file again this way, we can still debug the functions + +#define STBIR_strs_join2( start, mid, end ) start##mid##end +#define STBIR_strs_join1( start, mid, end ) STBIR_strs_join2( start, mid, end ) + +#define STBIR_strs_join24( start, mid1, mid2, end ) start##mid1##mid2##end +#define STBIR_strs_join14( start, mid1, mid2, end ) STBIR_strs_join24( start, mid1, mid2, end ) + +#ifdef STB_IMAGE_RESIZE_DO_CODERS + +#ifdef stbir__decode_suffix +#define STBIR__CODER_NAME( name ) STBIR_strs_join1( name, _, stbir__decode_suffix ) +#else +#define STBIR__CODER_NAME( name ) name +#endif + +#ifdef stbir__decode_swizzle +#define stbir__decode_simdf8_flip(reg) STBIR_strs_join1( STBIR_strs_join1( STBIR_strs_join1( STBIR_strs_join1( stbir__simdf8_0123to,stbir__decode_order0,stbir__decode_order1),stbir__decode_order2,stbir__decode_order3),stbir__decode_order0,stbir__decode_order1),stbir__decode_order2,stbir__decode_order3)(reg, reg) +#define stbir__decode_simdf4_flip(reg) STBIR_strs_join1( STBIR_strs_join1( stbir__simdf_0123to,stbir__decode_order0,stbir__decode_order1),stbir__decode_order2,stbir__decode_order3)(reg, reg) +#define stbir__encode_simdf8_unflip(reg) STBIR_strs_join1( STBIR_strs_join1( STBIR_strs_join1( STBIR_strs_join1( stbir__simdf8_0123to,stbir__encode_order0,stbir__encode_order1),stbir__encode_order2,stbir__encode_order3),stbir__encode_order0,stbir__encode_order1),stbir__encode_order2,stbir__encode_order3)(reg, reg) +#define stbir__encode_simdf4_unflip(reg) STBIR_strs_join1( STBIR_strs_join1( stbir__simdf_0123to,stbir__encode_order0,stbir__encode_order1),stbir__encode_order2,stbir__encode_order3)(reg, reg) +#else +#define stbir__decode_order0 0 +#define stbir__decode_order1 1 +#define stbir__decode_order2 2 +#define stbir__decode_order3 3 +#define stbir__encode_order0 0 +#define stbir__encode_order1 1 +#define stbir__encode_order2 2 +#define stbir__encode_order3 3 +#define stbir__decode_simdf8_flip(reg) +#define stbir__decode_simdf4_flip(reg) +#define stbir__encode_simdf8_unflip(reg) +#define stbir__encode_simdf4_unflip(reg) +#endif + +#ifdef STBIR_SIMD8 +#define stbir__encode_simdfX_unflip stbir__encode_simdf8_unflip +#else +#define stbir__encode_simdfX_unflip stbir__encode_simdf4_unflip +#endif + +static void STBIR__CODER_NAME( stbir__decode_uint8_linear_scaled )( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + unsigned char const * input = (unsigned char const*)inputp; + + #ifdef STBIR_SIMD + unsigned char const * end_input_m16 = input + width_times_channels - 16; + if ( width_times_channels >= 16 ) + { + decode_end -= 16; + for(;;) + { + #ifdef STBIR_SIMD8 + stbir__simdi i; stbir__simdi8 o0,o1; + stbir__simdf8 of0, of1; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi8_expand_u8_to_u32( o0, o1, i ); + stbir__simdi8_convert_i32_to_float( of0, o0 ); + stbir__simdi8_convert_i32_to_float( of1, o1 ); + stbir__simdf8_mult( of0, of0, STBIR_max_uint8_as_float_inverted8); + stbir__simdf8_mult( of1, of1, STBIR_max_uint8_as_float_inverted8); + stbir__decode_simdf8_flip( of0 ); + stbir__decode_simdf8_flip( of1 ); + stbir__simdf8_store( decode + 0, of0 ); + stbir__simdf8_store( decode + 8, of1 ); + #else + stbir__simdi i, o0, o1, o2, o3; + stbir__simdf of0, of1, of2, of3; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi_expand_u8_to_u32( o0,o1,o2,o3,i); + stbir__simdi_convert_i32_to_float( of0, o0 ); + stbir__simdi_convert_i32_to_float( of1, o1 ); + stbir__simdi_convert_i32_to_float( of2, o2 ); + stbir__simdi_convert_i32_to_float( of3, o3 ); + stbir__simdf_mult( of0, of0, STBIR__CONSTF(STBIR_max_uint8_as_float_inverted) ); + stbir__simdf_mult( of1, of1, STBIR__CONSTF(STBIR_max_uint8_as_float_inverted) ); + stbir__simdf_mult( of2, of2, STBIR__CONSTF(STBIR_max_uint8_as_float_inverted) ); + stbir__simdf_mult( of3, of3, STBIR__CONSTF(STBIR_max_uint8_as_float_inverted) ); + stbir__decode_simdf4_flip( of0 ); + stbir__decode_simdf4_flip( of1 ); + stbir__decode_simdf4_flip( of2 ); + stbir__decode_simdf4_flip( of3 ); + stbir__simdf_store( decode + 0, of0 ); + stbir__simdf_store( decode + 4, of1 ); + stbir__simdf_store( decode + 8, of2 ); + stbir__simdf_store( decode + 12, of3 ); + #endif + decode += 16; + input += 16; + if ( decode <= decode_end ) + continue; + if ( decode == ( decode_end + 16 ) ) + break; + decode = decode_end; // backup and do last couple + input = end_input_m16; + } + return; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + while( decode <= decode_end ) + { + STBIR_SIMD_NO_UNROLL(decode); + decode[0-4] = ((float)(input[stbir__decode_order0])) * stbir__max_uint8_as_float_inverted; + decode[1-4] = ((float)(input[stbir__decode_order1])) * stbir__max_uint8_as_float_inverted; + decode[2-4] = ((float)(input[stbir__decode_order2])) * stbir__max_uint8_as_float_inverted; + decode[3-4] = ((float)(input[stbir__decode_order3])) * stbir__max_uint8_as_float_inverted; + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = ((float)(input[stbir__decode_order0])) * stbir__max_uint8_as_float_inverted; + #if stbir__coder_min_num >= 2 + decode[1] = ((float)(input[stbir__decode_order1])) * stbir__max_uint8_as_float_inverted; + #endif + #if stbir__coder_min_num >= 3 + decode[2] = ((float)(input[stbir__decode_order2])) * stbir__max_uint8_as_float_inverted; + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif +} + +static void STBIR__CODER_NAME( stbir__encode_uint8_linear_scaled )( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned char STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned char *) outputp; + unsigned char * end_output = ( (unsigned char *) output ) + width_times_channels; + + #ifdef STBIR_SIMD + if ( width_times_channels >= stbir__simdfX_float_count*2 ) + { + float const * end_encode_m8 = encode + width_times_channels - stbir__simdfX_float_count*2; + end_output -= stbir__simdfX_float_count*2; + for(;;) + { + stbir__simdfX e0, e1; + stbir__simdi i; + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdfX_madd_mem( e0, STBIR_simd_point5X, STBIR_max_uint8_as_floatX, encode ); + stbir__simdfX_madd_mem( e1, STBIR_simd_point5X, STBIR_max_uint8_as_floatX, encode+stbir__simdfX_float_count ); + stbir__encode_simdfX_unflip( e0 ); + stbir__encode_simdfX_unflip( e1 ); + #ifdef STBIR_SIMD8 + stbir__simdf8_pack_to_16bytes( i, e0, e1 ); + stbir__simdi_store( output, i ); + #else + stbir__simdf_pack_to_8bytes( i, e0, e1 ); + stbir__simdi_store2( output, i ); + #endif + encode += stbir__simdfX_float_count*2; + output += stbir__simdfX_float_count*2; + if ( output <= end_output ) + continue; + if ( output == ( end_output + stbir__simdfX_float_count*2 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m8; + } + return; + } + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + stbir__simdf e0; + stbir__simdi i0; + STBIR_NO_UNROLL(encode); + stbir__simdf_load( e0, encode ); + stbir__simdf_madd( e0, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint8_as_float), e0 ); + stbir__encode_simdf4_unflip( e0 ); + stbir__simdf_pack_to_8bytes( i0, e0, e0 ); // only use first 4 + *(int*)(output-4) = stbir__simdi_to_int( i0 ); + output += 4; + encode += 4; + } + output -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( output < end_output ) + { + stbir__simdf e0; + STBIR_NO_UNROLL(encode); + stbir__simdf_madd1_mem( e0, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint8_as_float), encode+stbir__encode_order0 ); output[0] = stbir__simdf_convert_float_to_uint8( e0 ); + #if stbir__coder_min_num >= 2 + stbir__simdf_madd1_mem( e0, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint8_as_float), encode+stbir__encode_order1 ); output[1] = stbir__simdf_convert_float_to_uint8( e0 ); + #endif + #if stbir__coder_min_num >= 3 + stbir__simdf_madd1_mem( e0, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint8_as_float), encode+stbir__encode_order2 ); output[2] = stbir__simdf_convert_float_to_uint8( e0 ); + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif + + #else + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + float f; + f = encode[stbir__encode_order0] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[0-4] = (unsigned char)f; + f = encode[stbir__encode_order1] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[1-4] = (unsigned char)f; + f = encode[stbir__encode_order2] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[2-4] = (unsigned char)f; + f = encode[stbir__encode_order3] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[3-4] = (unsigned char)f; + output += 4; + encode += 4; + } + output -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( output < end_output ) + { + float f; + STBIR_NO_UNROLL(encode); + f = encode[stbir__encode_order0] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[0] = (unsigned char)f; + #if stbir__coder_min_num >= 2 + f = encode[stbir__encode_order1] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[1] = (unsigned char)f; + #endif + #if stbir__coder_min_num >= 3 + f = encode[stbir__encode_order2] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[2] = (unsigned char)f; + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif + #endif +} + +static void STBIR__CODER_NAME(stbir__decode_uint8_linear)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + unsigned char const * input = (unsigned char const*)inputp; + + #ifdef STBIR_SIMD + unsigned char const * end_input_m16 = input + width_times_channels - 16; + if ( width_times_channels >= 16 ) + { + decode_end -= 16; + for(;;) + { + #ifdef STBIR_SIMD8 + stbir__simdi i; stbir__simdi8 o0,o1; + stbir__simdf8 of0, of1; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi8_expand_u8_to_u32( o0, o1, i ); + stbir__simdi8_convert_i32_to_float( of0, o0 ); + stbir__simdi8_convert_i32_to_float( of1, o1 ); + stbir__decode_simdf8_flip( of0 ); + stbir__decode_simdf8_flip( of1 ); + stbir__simdf8_store( decode + 0, of0 ); + stbir__simdf8_store( decode + 8, of1 ); + #else + stbir__simdi i, o0, o1, o2, o3; + stbir__simdf of0, of1, of2, of3; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi_expand_u8_to_u32( o0,o1,o2,o3,i); + stbir__simdi_convert_i32_to_float( of0, o0 ); + stbir__simdi_convert_i32_to_float( of1, o1 ); + stbir__simdi_convert_i32_to_float( of2, o2 ); + stbir__simdi_convert_i32_to_float( of3, o3 ); + stbir__decode_simdf4_flip( of0 ); + stbir__decode_simdf4_flip( of1 ); + stbir__decode_simdf4_flip( of2 ); + stbir__decode_simdf4_flip( of3 ); + stbir__simdf_store( decode + 0, of0 ); + stbir__simdf_store( decode + 4, of1 ); + stbir__simdf_store( decode + 8, of2 ); + stbir__simdf_store( decode + 12, of3 ); +#endif + decode += 16; + input += 16; + if ( decode <= decode_end ) + continue; + if ( decode == ( decode_end + 16 ) ) + break; + decode = decode_end; // backup and do last couple + input = end_input_m16; + } + return; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + while( decode <= decode_end ) + { + STBIR_SIMD_NO_UNROLL(decode); + decode[0-4] = ((float)(input[stbir__decode_order0])); + decode[1-4] = ((float)(input[stbir__decode_order1])); + decode[2-4] = ((float)(input[stbir__decode_order2])); + decode[3-4] = ((float)(input[stbir__decode_order3])); + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = ((float)(input[stbir__decode_order0])); + #if stbir__coder_min_num >= 2 + decode[1] = ((float)(input[stbir__decode_order1])); + #endif + #if stbir__coder_min_num >= 3 + decode[2] = ((float)(input[stbir__decode_order2])); + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif +} + +static void STBIR__CODER_NAME( stbir__encode_uint8_linear )( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned char STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned char *) outputp; + unsigned char * end_output = ( (unsigned char *) output ) + width_times_channels; + + #ifdef STBIR_SIMD + if ( width_times_channels >= stbir__simdfX_float_count*2 ) + { + float const * end_encode_m8 = encode + width_times_channels - stbir__simdfX_float_count*2; + end_output -= stbir__simdfX_float_count*2; + for(;;) + { + stbir__simdfX e0, e1; + stbir__simdi i; + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdfX_add_mem( e0, STBIR_simd_point5X, encode ); + stbir__simdfX_add_mem( e1, STBIR_simd_point5X, encode+stbir__simdfX_float_count ); + stbir__encode_simdfX_unflip( e0 ); + stbir__encode_simdfX_unflip( e1 ); + #ifdef STBIR_SIMD8 + stbir__simdf8_pack_to_16bytes( i, e0, e1 ); + stbir__simdi_store( output, i ); + #else + stbir__simdf_pack_to_8bytes( i, e0, e1 ); + stbir__simdi_store2( output, i ); + #endif + encode += stbir__simdfX_float_count*2; + output += stbir__simdfX_float_count*2; + if ( output <= end_output ) + continue; + if ( output == ( end_output + stbir__simdfX_float_count*2 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m8; + } + return; + } + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + stbir__simdf e0; + stbir__simdi i0; + STBIR_NO_UNROLL(encode); + stbir__simdf_load( e0, encode ); + stbir__simdf_add( e0, STBIR__CONSTF(STBIR_simd_point5), e0 ); + stbir__encode_simdf4_unflip( e0 ); + stbir__simdf_pack_to_8bytes( i0, e0, e0 ); // only use first 4 + *(int*)(output-4) = stbir__simdi_to_int( i0 ); + output += 4; + encode += 4; + } + output -= 4; + #endif + + #else + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + float f; + f = encode[stbir__encode_order0] + 0.5f; STBIR_CLAMP(f, 0, 255); output[0-4] = (unsigned char)f; + f = encode[stbir__encode_order1] + 0.5f; STBIR_CLAMP(f, 0, 255); output[1-4] = (unsigned char)f; + f = encode[stbir__encode_order2] + 0.5f; STBIR_CLAMP(f, 0, 255); output[2-4] = (unsigned char)f; + f = encode[stbir__encode_order3] + 0.5f; STBIR_CLAMP(f, 0, 255); output[3-4] = (unsigned char)f; + output += 4; + encode += 4; + } + output -= 4; + #endif + + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( output < end_output ) + { + float f; + STBIR_NO_UNROLL(encode); + f = encode[stbir__encode_order0] + 0.5f; STBIR_CLAMP(f, 0, 255); output[0] = (unsigned char)f; + #if stbir__coder_min_num >= 2 + f = encode[stbir__encode_order1] + 0.5f; STBIR_CLAMP(f, 0, 255); output[1] = (unsigned char)f; + #endif + #if stbir__coder_min_num >= 3 + f = encode[stbir__encode_order2] + 0.5f; STBIR_CLAMP(f, 0, 255); output[2] = (unsigned char)f; + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif +} + +static void STBIR__CODER_NAME(stbir__decode_uint8_srgb)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float const * decode_end = (float*) decode + width_times_channels; + unsigned char const * input = (unsigned char const *)inputp; + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + while( decode <= decode_end ) + { + decode[0-4] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order0 ] ]; + decode[1-4] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order1 ] ]; + decode[2-4] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order2 ] ]; + decode[3-4] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order3 ] ]; + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order0 ] ]; + #if stbir__coder_min_num >= 2 + decode[1] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order1 ] ]; + #endif + #if stbir__coder_min_num >= 3 + decode[2] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order2 ] ]; + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif +} + +#define stbir__min_max_shift20( i, f ) \ + stbir__simdf_max( f, f, stbir_simdf_casti(STBIR__CONSTI( STBIR_almost_zero )) ); \ + stbir__simdf_min( f, f, stbir_simdf_casti(STBIR__CONSTI( STBIR_almost_one )) ); \ + stbir__simdi_32shr( i, stbir_simdi_castf( f ), 20 ); + +#define stbir__scale_and_convert( i, f ) \ + stbir__simdf_madd( f, STBIR__CONSTF( STBIR_simd_point5 ), STBIR__CONSTF( STBIR_max_uint8_as_float ), f ); \ + stbir__simdf_max( f, f, stbir__simdf_zeroP() ); \ + stbir__simdf_min( f, f, STBIR__CONSTF( STBIR_max_uint8_as_float ) ); \ + stbir__simdf_convert_float_to_i32( i, f ); + +#define stbir__linear_to_srgb_finish( i, f ) \ +{ \ + stbir__simdi temp; \ + stbir__simdi_32shr( temp, stbir_simdi_castf( f ), 12 ) ; \ + stbir__simdi_and( temp, temp, STBIR__CONSTI(STBIR_mastissa_mask) ); \ + stbir__simdi_or( temp, temp, STBIR__CONSTI(STBIR_topscale) ); \ + stbir__simdi_16madd( i, i, temp ); \ + stbir__simdi_32shr( i, i, 16 ); \ +} + +#define stbir__simdi_table_lookup2( v0,v1, table ) \ +{ \ + stbir__simdi_u32 temp0,temp1; \ + temp0.m128i_i128 = v0; \ + temp1.m128i_i128 = v1; \ + temp0.m128i_u32[0] = table[temp0.m128i_i32[0]]; temp0.m128i_u32[1] = table[temp0.m128i_i32[1]]; temp0.m128i_u32[2] = table[temp0.m128i_i32[2]]; temp0.m128i_u32[3] = table[temp0.m128i_i32[3]]; \ + temp1.m128i_u32[0] = table[temp1.m128i_i32[0]]; temp1.m128i_u32[1] = table[temp1.m128i_i32[1]]; temp1.m128i_u32[2] = table[temp1.m128i_i32[2]]; temp1.m128i_u32[3] = table[temp1.m128i_i32[3]]; \ + v0 = temp0.m128i_i128; \ + v1 = temp1.m128i_i128; \ +} + +#define stbir__simdi_table_lookup3( v0,v1,v2, table ) \ +{ \ + stbir__simdi_u32 temp0,temp1,temp2; \ + temp0.m128i_i128 = v0; \ + temp1.m128i_i128 = v1; \ + temp2.m128i_i128 = v2; \ + temp0.m128i_u32[0] = table[temp0.m128i_i32[0]]; temp0.m128i_u32[1] = table[temp0.m128i_i32[1]]; temp0.m128i_u32[2] = table[temp0.m128i_i32[2]]; temp0.m128i_u32[3] = table[temp0.m128i_i32[3]]; \ + temp1.m128i_u32[0] = table[temp1.m128i_i32[0]]; temp1.m128i_u32[1] = table[temp1.m128i_i32[1]]; temp1.m128i_u32[2] = table[temp1.m128i_i32[2]]; temp1.m128i_u32[3] = table[temp1.m128i_i32[3]]; \ + temp2.m128i_u32[0] = table[temp2.m128i_i32[0]]; temp2.m128i_u32[1] = table[temp2.m128i_i32[1]]; temp2.m128i_u32[2] = table[temp2.m128i_i32[2]]; temp2.m128i_u32[3] = table[temp2.m128i_i32[3]]; \ + v0 = temp0.m128i_i128; \ + v1 = temp1.m128i_i128; \ + v2 = temp2.m128i_i128; \ +} + +#define stbir__simdi_table_lookup4( v0,v1,v2,v3, table ) \ +{ \ + stbir__simdi_u32 temp0,temp1,temp2,temp3; \ + temp0.m128i_i128 = v0; \ + temp1.m128i_i128 = v1; \ + temp2.m128i_i128 = v2; \ + temp3.m128i_i128 = v3; \ + temp0.m128i_u32[0] = table[temp0.m128i_i32[0]]; temp0.m128i_u32[1] = table[temp0.m128i_i32[1]]; temp0.m128i_u32[2] = table[temp0.m128i_i32[2]]; temp0.m128i_u32[3] = table[temp0.m128i_i32[3]]; \ + temp1.m128i_u32[0] = table[temp1.m128i_i32[0]]; temp1.m128i_u32[1] = table[temp1.m128i_i32[1]]; temp1.m128i_u32[2] = table[temp1.m128i_i32[2]]; temp1.m128i_u32[3] = table[temp1.m128i_i32[3]]; \ + temp2.m128i_u32[0] = table[temp2.m128i_i32[0]]; temp2.m128i_u32[1] = table[temp2.m128i_i32[1]]; temp2.m128i_u32[2] = table[temp2.m128i_i32[2]]; temp2.m128i_u32[3] = table[temp2.m128i_i32[3]]; \ + temp3.m128i_u32[0] = table[temp3.m128i_i32[0]]; temp3.m128i_u32[1] = table[temp3.m128i_i32[1]]; temp3.m128i_u32[2] = table[temp3.m128i_i32[2]]; temp3.m128i_u32[3] = table[temp3.m128i_i32[3]]; \ + v0 = temp0.m128i_i128; \ + v1 = temp1.m128i_i128; \ + v2 = temp2.m128i_i128; \ + v3 = temp3.m128i_i128; \ +} + +static void STBIR__CODER_NAME( stbir__encode_uint8_srgb )( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned char STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned char*) outputp; + unsigned char * end_output = ( (unsigned char*) output ) + width_times_channels; + + #ifdef STBIR_SIMD + stbir_uint32 const * to_srgb = fp32_to_srgb8_tab4 - (127-13)*8; + + if ( width_times_channels >= 16 ) + { + float const * end_encode_m16 = encode + width_times_channels - 16; + end_output -= 16; + for(;;) + { + stbir__simdf f0, f1, f2, f3; + stbir__simdi i0, i1, i2, i3; + STBIR_SIMD_NO_UNROLL(encode); + + stbir__simdf_load4_transposed( f0, f1, f2, f3, encode ); + + stbir__min_max_shift20( i0, f0 ); + stbir__min_max_shift20( i1, f1 ); + stbir__min_max_shift20( i2, f2 ); + stbir__min_max_shift20( i3, f3 ); + + stbir__simdi_table_lookup4( i0, i1, i2, i3, to_srgb ); + + stbir__linear_to_srgb_finish( i0, f0 ); + stbir__linear_to_srgb_finish( i1, f1 ); + stbir__linear_to_srgb_finish( i2, f2 ); + stbir__linear_to_srgb_finish( i3, f3 ); + + stbir__interleave_pack_and_store_16_u8( output, STBIR_strs_join1(i, ,stbir__encode_order0), STBIR_strs_join1(i, ,stbir__encode_order1), STBIR_strs_join1(i, ,stbir__encode_order2), STBIR_strs_join1(i, ,stbir__encode_order3) ); + + encode += 16; + output += 16; + if ( output <= end_output ) + continue; + if ( output == ( end_output + 16 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m16; + } + return; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while ( output <= end_output ) + { + STBIR_SIMD_NO_UNROLL(encode); + + output[0-4] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order0] ); + output[1-4] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order1] ); + output[2-4] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order2] ); + output[3-4] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order3] ); + + output += 4; + encode += 4; + } + output -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( output < end_output ) + { + STBIR_NO_UNROLL(encode); + output[0] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order0] ); + #if stbir__coder_min_num >= 2 + output[1] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order1] ); + #endif + #if stbir__coder_min_num >= 3 + output[2] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order2] ); + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif +} + +#if ( stbir__coder_min_num == 4 ) || ( ( stbir__coder_min_num == 1 ) && ( !defined(stbir__decode_swizzle) ) ) + +static void STBIR__CODER_NAME(stbir__decode_uint8_srgb4_linearalpha)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float const * decode_end = (float*) decode + width_times_channels; + unsigned char const * input = (unsigned char const *)inputp; + do { + decode[0] = stbir__srgb_uchar_to_linear_float[ input[stbir__decode_order0] ]; + decode[1] = stbir__srgb_uchar_to_linear_float[ input[stbir__decode_order1] ]; + decode[2] = stbir__srgb_uchar_to_linear_float[ input[stbir__decode_order2] ]; + decode[3] = ( (float) input[stbir__decode_order3] ) * stbir__max_uint8_as_float_inverted; + input += 4; + decode += 4; + } while( decode < decode_end ); +} + + +static void STBIR__CODER_NAME( stbir__encode_uint8_srgb4_linearalpha )( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned char STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned char*) outputp; + unsigned char * end_output = ( (unsigned char*) output ) + width_times_channels; + + #ifdef STBIR_SIMD + stbir_uint32 const * to_srgb = fp32_to_srgb8_tab4 - (127-13)*8; + + if ( width_times_channels >= 16 ) + { + float const * end_encode_m16 = encode + width_times_channels - 16; + end_output -= 16; + for(;;) + { + stbir__simdf f0, f1, f2, f3; + stbir__simdi i0, i1, i2, i3; + + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdf_load4_transposed( f0, f1, f2, f3, encode ); + + stbir__min_max_shift20( i0, f0 ); + stbir__min_max_shift20( i1, f1 ); + stbir__min_max_shift20( i2, f2 ); + stbir__scale_and_convert( i3, f3 ); + + stbir__simdi_table_lookup3( i0, i1, i2, to_srgb ); + + stbir__linear_to_srgb_finish( i0, f0 ); + stbir__linear_to_srgb_finish( i1, f1 ); + stbir__linear_to_srgb_finish( i2, f2 ); + + stbir__interleave_pack_and_store_16_u8( output, STBIR_strs_join1(i, ,stbir__encode_order0), STBIR_strs_join1(i, ,stbir__encode_order1), STBIR_strs_join1(i, ,stbir__encode_order2), STBIR_strs_join1(i, ,stbir__encode_order3) ); + + output += 16; + encode += 16; + + if ( output <= end_output ) + continue; + if ( output == ( end_output + 16 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m16; + } + return; + } + #endif + + do { + float f; + STBIR_SIMD_NO_UNROLL(encode); + + output[stbir__decode_order0] = stbir__linear_to_srgb_uchar( encode[0] ); + output[stbir__decode_order1] = stbir__linear_to_srgb_uchar( encode[1] ); + output[stbir__decode_order2] = stbir__linear_to_srgb_uchar( encode[2] ); + + f = encode[3] * stbir__max_uint8_as_float + 0.5f; + STBIR_CLAMP(f, 0, 255); + output[stbir__decode_order3] = (unsigned char) f; + + output += 4; + encode += 4; + } while( output < end_output ); +} + +#endif + +#if ( stbir__coder_min_num == 2 ) || ( ( stbir__coder_min_num == 1 ) && ( !defined(stbir__decode_swizzle) ) ) + +static void STBIR__CODER_NAME(stbir__decode_uint8_srgb2_linearalpha)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float const * decode_end = (float*) decode + width_times_channels; + unsigned char const * input = (unsigned char const *)inputp; + decode += 4; + while( decode <= decode_end ) + { + decode[0-4] = stbir__srgb_uchar_to_linear_float[ input[stbir__decode_order0] ]; + decode[1-4] = ( (float) input[stbir__decode_order1] ) * stbir__max_uint8_as_float_inverted; + decode[2-4] = stbir__srgb_uchar_to_linear_float[ input[stbir__decode_order0+2] ]; + decode[3-4] = ( (float) input[stbir__decode_order1+2] ) * stbir__max_uint8_as_float_inverted; + input += 4; + decode += 4; + } + decode -= 4; + if( decode < decode_end ) + { + decode[0] = stbir__srgb_uchar_to_linear_float[ stbir__decode_order0 ]; + decode[1] = ( (float) input[stbir__decode_order1] ) * stbir__max_uint8_as_float_inverted; + } +} + +static void STBIR__CODER_NAME( stbir__encode_uint8_srgb2_linearalpha )( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned char STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned char*) outputp; + unsigned char * end_output = ( (unsigned char*) output ) + width_times_channels; + + #ifdef STBIR_SIMD + stbir_uint32 const * to_srgb = fp32_to_srgb8_tab4 - (127-13)*8; + + if ( width_times_channels >= 16 ) + { + float const * end_encode_m16 = encode + width_times_channels - 16; + end_output -= 16; + for(;;) + { + stbir__simdf f0, f1, f2, f3; + stbir__simdi i0, i1, i2, i3; + + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdf_load4_transposed( f0, f1, f2, f3, encode ); + + stbir__min_max_shift20( i0, f0 ); + stbir__scale_and_convert( i1, f1 ); + stbir__min_max_shift20( i2, f2 ); + stbir__scale_and_convert( i3, f3 ); + + stbir__simdi_table_lookup2( i0, i2, to_srgb ); + + stbir__linear_to_srgb_finish( i0, f0 ); + stbir__linear_to_srgb_finish( i2, f2 ); + + stbir__interleave_pack_and_store_16_u8( output, STBIR_strs_join1(i, ,stbir__encode_order0), STBIR_strs_join1(i, ,stbir__encode_order1), STBIR_strs_join1(i, ,stbir__encode_order2), STBIR_strs_join1(i, ,stbir__encode_order3) ); + + output += 16; + encode += 16; + if ( output <= end_output ) + continue; + if ( output == ( end_output + 16 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m16; + } + return; + } + #endif + + do { + float f; + STBIR_SIMD_NO_UNROLL(encode); + + output[stbir__decode_order0] = stbir__linear_to_srgb_uchar( encode[0] ); + + f = encode[1] * stbir__max_uint8_as_float + 0.5f; + STBIR_CLAMP(f, 0, 255); + output[stbir__decode_order1] = (unsigned char) f; + + output += 2; + encode += 2; + } while( output < end_output ); +} + +#endif + +static void STBIR__CODER_NAME(stbir__decode_uint16_linear_scaled)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + unsigned short const * input = (unsigned short const *)inputp; + + #ifdef STBIR_SIMD + unsigned short const * end_input_m8 = input + width_times_channels - 8; + if ( width_times_channels >= 8 ) + { + decode_end -= 8; + for(;;) + { + #ifdef STBIR_SIMD8 + stbir__simdi i; stbir__simdi8 o; + stbir__simdf8 of; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi8_expand_u16_to_u32( o, i ); + stbir__simdi8_convert_i32_to_float( of, o ); + stbir__simdf8_mult( of, of, STBIR_max_uint16_as_float_inverted8); + stbir__decode_simdf8_flip( of ); + stbir__simdf8_store( decode + 0, of ); + #else + stbir__simdi i, o0, o1; + stbir__simdf of0, of1; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi_expand_u16_to_u32( o0,o1,i ); + stbir__simdi_convert_i32_to_float( of0, o0 ); + stbir__simdi_convert_i32_to_float( of1, o1 ); + stbir__simdf_mult( of0, of0, STBIR__CONSTF(STBIR_max_uint16_as_float_inverted) ); + stbir__simdf_mult( of1, of1, STBIR__CONSTF(STBIR_max_uint16_as_float_inverted)); + stbir__decode_simdf4_flip( of0 ); + stbir__decode_simdf4_flip( of1 ); + stbir__simdf_store( decode + 0, of0 ); + stbir__simdf_store( decode + 4, of1 ); + #endif + decode += 8; + input += 8; + if ( decode <= decode_end ) + continue; + if ( decode == ( decode_end + 8 ) ) + break; + decode = decode_end; // backup and do last couple + input = end_input_m8; + } + return; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + while( decode <= decode_end ) + { + STBIR_SIMD_NO_UNROLL(decode); + decode[0-4] = ((float)(input[stbir__decode_order0])) * stbir__max_uint16_as_float_inverted; + decode[1-4] = ((float)(input[stbir__decode_order1])) * stbir__max_uint16_as_float_inverted; + decode[2-4] = ((float)(input[stbir__decode_order2])) * stbir__max_uint16_as_float_inverted; + decode[3-4] = ((float)(input[stbir__decode_order3])) * stbir__max_uint16_as_float_inverted; + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = ((float)(input[stbir__decode_order0])) * stbir__max_uint16_as_float_inverted; + #if stbir__coder_min_num >= 2 + decode[1] = ((float)(input[stbir__decode_order1])) * stbir__max_uint16_as_float_inverted; + #endif + #if stbir__coder_min_num >= 3 + decode[2] = ((float)(input[stbir__decode_order2])) * stbir__max_uint16_as_float_inverted; + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif +} + + +static void STBIR__CODER_NAME(stbir__encode_uint16_linear_scaled)( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned short STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned short*) outputp; + unsigned short * end_output = ( (unsigned short*) output ) + width_times_channels; + + #ifdef STBIR_SIMD + { + if ( width_times_channels >= stbir__simdfX_float_count*2 ) + { + float const * end_encode_m8 = encode + width_times_channels - stbir__simdfX_float_count*2; + end_output -= stbir__simdfX_float_count*2; + for(;;) + { + stbir__simdfX e0, e1; + stbir__simdiX i; + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdfX_madd_mem( e0, STBIR_simd_point5X, STBIR_max_uint16_as_floatX, encode ); + stbir__simdfX_madd_mem( e1, STBIR_simd_point5X, STBIR_max_uint16_as_floatX, encode+stbir__simdfX_float_count ); + stbir__encode_simdfX_unflip( e0 ); + stbir__encode_simdfX_unflip( e1 ); + stbir__simdfX_pack_to_words( i, e0, e1 ); + stbir__simdiX_store( output, i ); + encode += stbir__simdfX_float_count*2; + output += stbir__simdfX_float_count*2; + if ( output <= end_output ) + continue; + if ( output == ( end_output + stbir__simdfX_float_count*2 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m8; + } + return; + } + } + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + stbir__simdf e; + stbir__simdi i; + STBIR_NO_UNROLL(encode); + stbir__simdf_load( e, encode ); + stbir__simdf_madd( e, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint16_as_float), e ); + stbir__encode_simdf4_unflip( e ); + stbir__simdf_pack_to_8words( i, e, e ); // only use first 4 + stbir__simdi_store2( output-4, i ); + output += 4; + encode += 4; + } + output -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( output < end_output ) + { + stbir__simdf e; + STBIR_NO_UNROLL(encode); + stbir__simdf_madd1_mem( e, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint16_as_float), encode+stbir__encode_order0 ); output[0] = stbir__simdf_convert_float_to_short( e ); + #if stbir__coder_min_num >= 2 + stbir__simdf_madd1_mem( e, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint16_as_float), encode+stbir__encode_order1 ); output[1] = stbir__simdf_convert_float_to_short( e ); + #endif + #if stbir__coder_min_num >= 3 + stbir__simdf_madd1_mem( e, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint16_as_float), encode+stbir__encode_order2 ); output[2] = stbir__simdf_convert_float_to_short( e ); + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif + + #else + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + float f; + STBIR_SIMD_NO_UNROLL(encode); + f = encode[stbir__encode_order0] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[0-4] = (unsigned short)f; + f = encode[stbir__encode_order1] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[1-4] = (unsigned short)f; + f = encode[stbir__encode_order2] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[2-4] = (unsigned short)f; + f = encode[stbir__encode_order3] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[3-4] = (unsigned short)f; + output += 4; + encode += 4; + } + output -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( output < end_output ) + { + float f; + STBIR_NO_UNROLL(encode); + f = encode[stbir__encode_order0] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[0] = (unsigned short)f; + #if stbir__coder_min_num >= 2 + f = encode[stbir__encode_order1] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[1] = (unsigned short)f; + #endif + #if stbir__coder_min_num >= 3 + f = encode[stbir__encode_order2] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[2] = (unsigned short)f; + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif + #endif +} + +static void STBIR__CODER_NAME(stbir__decode_uint16_linear)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + unsigned short const * input = (unsigned short const *)inputp; + + #ifdef STBIR_SIMD + unsigned short const * end_input_m8 = input + width_times_channels - 8; + if ( width_times_channels >= 8 ) + { + decode_end -= 8; + for(;;) + { + #ifdef STBIR_SIMD8 + stbir__simdi i; stbir__simdi8 o; + stbir__simdf8 of; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi8_expand_u16_to_u32( o, i ); + stbir__simdi8_convert_i32_to_float( of, o ); + stbir__decode_simdf8_flip( of ); + stbir__simdf8_store( decode + 0, of ); + #else + stbir__simdi i, o0, o1; + stbir__simdf of0, of1; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi_expand_u16_to_u32( o0, o1, i ); + stbir__simdi_convert_i32_to_float( of0, o0 ); + stbir__simdi_convert_i32_to_float( of1, o1 ); + stbir__decode_simdf4_flip( of0 ); + stbir__decode_simdf4_flip( of1 ); + stbir__simdf_store( decode + 0, of0 ); + stbir__simdf_store( decode + 4, of1 ); + #endif + decode += 8; + input += 8; + if ( decode <= decode_end ) + continue; + if ( decode == ( decode_end + 8 ) ) + break; + decode = decode_end; // backup and do last couple + input = end_input_m8; + } + return; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + while( decode <= decode_end ) + { + STBIR_SIMD_NO_UNROLL(decode); + decode[0-4] = ((float)(input[stbir__decode_order0])); + decode[1-4] = ((float)(input[stbir__decode_order1])); + decode[2-4] = ((float)(input[stbir__decode_order2])); + decode[3-4] = ((float)(input[stbir__decode_order3])); + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = ((float)(input[stbir__decode_order0])); + #if stbir__coder_min_num >= 2 + decode[1] = ((float)(input[stbir__decode_order1])); + #endif + #if stbir__coder_min_num >= 3 + decode[2] = ((float)(input[stbir__decode_order2])); + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif +} + +static void STBIR__CODER_NAME(stbir__encode_uint16_linear)( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned short STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned short*) outputp; + unsigned short * end_output = ( (unsigned short*) output ) + width_times_channels; + + #ifdef STBIR_SIMD + { + if ( width_times_channels >= stbir__simdfX_float_count*2 ) + { + float const * end_encode_m8 = encode + width_times_channels - stbir__simdfX_float_count*2; + end_output -= stbir__simdfX_float_count*2; + for(;;) + { + stbir__simdfX e0, e1; + stbir__simdiX i; + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdfX_add_mem( e0, STBIR_simd_point5X, encode ); + stbir__simdfX_add_mem( e1, STBIR_simd_point5X, encode+stbir__simdfX_float_count ); + stbir__encode_simdfX_unflip( e0 ); + stbir__encode_simdfX_unflip( e1 ); + stbir__simdfX_pack_to_words( i, e0, e1 ); + stbir__simdiX_store( output, i ); + encode += stbir__simdfX_float_count*2; + output += stbir__simdfX_float_count*2; + if ( output <= end_output ) + continue; + if ( output == ( end_output + stbir__simdfX_float_count*2 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m8; + } + return; + } + } + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + stbir__simdf e; + stbir__simdi i; + STBIR_NO_UNROLL(encode); + stbir__simdf_load( e, encode ); + stbir__simdf_add( e, STBIR__CONSTF(STBIR_simd_point5), e ); + stbir__encode_simdf4_unflip( e ); + stbir__simdf_pack_to_8words( i, e, e ); // only use first 4 + stbir__simdi_store2( output-4, i ); + output += 4; + encode += 4; + } + output -= 4; + #endif + + #else + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + float f; + STBIR_SIMD_NO_UNROLL(encode); + f = encode[stbir__encode_order0] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[0-4] = (unsigned short)f; + f = encode[stbir__encode_order1] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[1-4] = (unsigned short)f; + f = encode[stbir__encode_order2] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[2-4] = (unsigned short)f; + f = encode[stbir__encode_order3] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[3-4] = (unsigned short)f; + output += 4; + encode += 4; + } + output -= 4; + #endif + + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( output < end_output ) + { + float f; + STBIR_NO_UNROLL(encode); + f = encode[stbir__encode_order0] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[0] = (unsigned short)f; + #if stbir__coder_min_num >= 2 + f = encode[stbir__encode_order1] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[1] = (unsigned short)f; + #endif + #if stbir__coder_min_num >= 3 + f = encode[stbir__encode_order2] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[2] = (unsigned short)f; + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif +} + +static void STBIR__CODER_NAME(stbir__decode_half_float_linear)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + stbir__FP16 const * input = (stbir__FP16 const *)inputp; + + #ifdef STBIR_SIMD + if ( width_times_channels >= 8 ) + { + stbir__FP16 const * end_input_m8 = input + width_times_channels - 8; + decode_end -= 8; + for(;;) + { + STBIR_NO_UNROLL(decode); + + stbir__half_to_float_SIMD( decode, input ); + #ifdef stbir__decode_swizzle + #ifdef STBIR_SIMD8 + { + stbir__simdf8 of; + stbir__simdf8_load( of, decode ); + stbir__decode_simdf8_flip( of ); + stbir__simdf8_store( decode, of ); + } + #else + { + stbir__simdf of0,of1; + stbir__simdf_load( of0, decode ); + stbir__simdf_load( of1, decode+4 ); + stbir__decode_simdf4_flip( of0 ); + stbir__decode_simdf4_flip( of1 ); + stbir__simdf_store( decode, of0 ); + stbir__simdf_store( decode+4, of1 ); + } + #endif + #endif + decode += 8; + input += 8; + if ( decode <= decode_end ) + continue; + if ( decode == ( decode_end + 8 ) ) + break; + decode = decode_end; // backup and do last couple + input = end_input_m8; + } + return; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + while( decode <= decode_end ) + { + STBIR_SIMD_NO_UNROLL(decode); + decode[0-4] = stbir__half_to_float(input[stbir__decode_order0]); + decode[1-4] = stbir__half_to_float(input[stbir__decode_order1]); + decode[2-4] = stbir__half_to_float(input[stbir__decode_order2]); + decode[3-4] = stbir__half_to_float(input[stbir__decode_order3]); + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = stbir__half_to_float(input[stbir__decode_order0]); + #if stbir__coder_min_num >= 2 + decode[1] = stbir__half_to_float(input[stbir__decode_order1]); + #endif + #if stbir__coder_min_num >= 3 + decode[2] = stbir__half_to_float(input[stbir__decode_order2]); + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif +} + +static void STBIR__CODER_NAME( stbir__encode_half_float_linear )( void * outputp, int width_times_channels, float const * encode ) +{ + stbir__FP16 STBIR_SIMD_STREAMOUT_PTR( * ) output = (stbir__FP16*) outputp; + stbir__FP16 * end_output = ( (stbir__FP16*) output ) + width_times_channels; + + #ifdef STBIR_SIMD + if ( width_times_channels >= 8 ) + { + float const * end_encode_m8 = encode + width_times_channels - 8; + end_output -= 8; + for(;;) + { + STBIR_SIMD_NO_UNROLL(encode); + #ifdef stbir__decode_swizzle + #ifdef STBIR_SIMD8 + { + stbir__simdf8 of; + stbir__simdf8_load( of, encode ); + stbir__encode_simdf8_unflip( of ); + stbir__float_to_half_SIMD( output, (float*)&of ); + } + #else + { + stbir__simdf of[2]; + stbir__simdf_load( of[0], encode ); + stbir__simdf_load( of[1], encode+4 ); + stbir__encode_simdf4_unflip( of[0] ); + stbir__encode_simdf4_unflip( of[1] ); + stbir__float_to_half_SIMD( output, (float*)of ); + } + #endif + #else + stbir__float_to_half_SIMD( output, encode ); + #endif + encode += 8; + output += 8; + if ( output <= end_output ) + continue; + if ( output == ( end_output + 8 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m8; + } + return; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + STBIR_SIMD_NO_UNROLL(output); + output[0-4] = stbir__float_to_half(encode[stbir__encode_order0]); + output[1-4] = stbir__float_to_half(encode[stbir__encode_order1]); + output[2-4] = stbir__float_to_half(encode[stbir__encode_order2]); + output[3-4] = stbir__float_to_half(encode[stbir__encode_order3]); + output += 4; + encode += 4; + } + output -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( output < end_output ) + { + STBIR_NO_UNROLL(output); + output[0] = stbir__float_to_half(encode[stbir__encode_order0]); + #if stbir__coder_min_num >= 2 + output[1] = stbir__float_to_half(encode[stbir__encode_order1]); + #endif + #if stbir__coder_min_num >= 3 + output[2] = stbir__float_to_half(encode[stbir__encode_order2]); + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif +} + +static void STBIR__CODER_NAME(stbir__decode_float_linear)( float * decodep, int width_times_channels, void const * inputp ) +{ + #ifdef stbir__decode_swizzle + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + float const * input = (float const *)inputp; + + #ifdef STBIR_SIMD + if ( width_times_channels >= 16 ) + { + float const * end_input_m16 = input + width_times_channels - 16; + decode_end -= 16; + for(;;) + { + STBIR_NO_UNROLL(decode); + #ifdef stbir__decode_swizzle + #ifdef STBIR_SIMD8 + { + stbir__simdf8 of0,of1; + stbir__simdf8_load( of0, input ); + stbir__simdf8_load( of1, input+8 ); + stbir__decode_simdf8_flip( of0 ); + stbir__decode_simdf8_flip( of1 ); + stbir__simdf8_store( decode, of0 ); + stbir__simdf8_store( decode+8, of1 ); + } + #else + { + stbir__simdf of0,of1,of2,of3; + stbir__simdf_load( of0, input ); + stbir__simdf_load( of1, input+4 ); + stbir__simdf_load( of2, input+8 ); + stbir__simdf_load( of3, input+12 ); + stbir__decode_simdf4_flip( of0 ); + stbir__decode_simdf4_flip( of1 ); + stbir__decode_simdf4_flip( of2 ); + stbir__decode_simdf4_flip( of3 ); + stbir__simdf_store( decode, of0 ); + stbir__simdf_store( decode+4, of1 ); + stbir__simdf_store( decode+8, of2 ); + stbir__simdf_store( decode+12, of3 ); + } + #endif + #endif + decode += 16; + input += 16; + if ( decode <= decode_end ) + continue; + if ( decode == ( decode_end + 16 ) ) + break; + decode = decode_end; // backup and do last couple + input = end_input_m16; + } + return; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + while( decode <= decode_end ) + { + STBIR_SIMD_NO_UNROLL(decode); + decode[0-4] = input[stbir__decode_order0]; + decode[1-4] = input[stbir__decode_order1]; + decode[2-4] = input[stbir__decode_order2]; + decode[3-4] = input[stbir__decode_order3]; + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = input[stbir__decode_order0]; + #if stbir__coder_min_num >= 2 + decode[1] = input[stbir__decode_order1]; + #endif + #if stbir__coder_min_num >= 3 + decode[2] = input[stbir__decode_order2]; + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif + + #else + + if ( (void*)decodep != inputp ) + STBIR_MEMCPY( decodep, inputp, width_times_channels * sizeof( float ) ); + + #endif +} + +static void STBIR__CODER_NAME( stbir__encode_float_linear )( void * outputp, int width_times_channels, float const * encode ) +{ + #if !defined( STBIR_FLOAT_HIGH_CLAMP ) && !defined(STBIR_FLOAT_LO_CLAMP) && !defined(stbir__decode_swizzle) + + if ( (void*)outputp != (void*) encode ) + STBIR_MEMCPY( outputp, encode, width_times_channels * sizeof( float ) ); + + #else + + float STBIR_SIMD_STREAMOUT_PTR( * ) output = (float*) outputp; + float * end_output = ( (float*) output ) + width_times_channels; + + #ifdef STBIR_FLOAT_HIGH_CLAMP + #define stbir_scalar_hi_clamp( v ) if ( v > STBIR_FLOAT_HIGH_CLAMP ) v = STBIR_FLOAT_HIGH_CLAMP; + #else + #define stbir_scalar_hi_clamp( v ) + #endif + #ifdef STBIR_FLOAT_LOW_CLAMP + #define stbir_scalar_lo_clamp( v ) if ( v < STBIR_FLOAT_LOW_CLAMP ) v = STBIR_FLOAT_LOW_CLAMP; + #else + #define stbir_scalar_lo_clamp( v ) + #endif + + #ifdef STBIR_SIMD + + #ifdef STBIR_FLOAT_HIGH_CLAMP + const stbir__simdfX high_clamp = stbir__simdf_frepX(STBIR_FLOAT_HIGH_CLAMP); + #endif + #ifdef STBIR_FLOAT_LOW_CLAMP + const stbir__simdfX low_clamp = stbir__simdf_frepX(STBIR_FLOAT_LOW_CLAMP); + #endif + + if ( width_times_channels >= ( stbir__simdfX_float_count * 2 ) ) + { + float const * end_encode_m8 = encode + width_times_channels - ( stbir__simdfX_float_count * 2 ); + end_output -= ( stbir__simdfX_float_count * 2 ); + for(;;) + { + stbir__simdfX e0, e1; + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdfX_load( e0, encode ); + stbir__simdfX_load( e1, encode+stbir__simdfX_float_count ); +#ifdef STBIR_FLOAT_HIGH_CLAMP + stbir__simdfX_min( e0, e0, high_clamp ); + stbir__simdfX_min( e1, e1, high_clamp ); +#endif +#ifdef STBIR_FLOAT_LOW_CLAMP + stbir__simdfX_max( e0, e0, low_clamp ); + stbir__simdfX_max( e1, e1, low_clamp ); +#endif + stbir__encode_simdfX_unflip( e0 ); + stbir__encode_simdfX_unflip( e1 ); + stbir__simdfX_store( output, e0 ); + stbir__simdfX_store( output+stbir__simdfX_float_count, e1 ); + encode += stbir__simdfX_float_count * 2; + output += stbir__simdfX_float_count * 2; + if ( output < end_output ) + continue; + if ( output == ( end_output + ( stbir__simdfX_float_count * 2 ) ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m8; + } + return; + } + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + stbir__simdf e0; + STBIR_NO_UNROLL(encode); + stbir__simdf_load( e0, encode ); +#ifdef STBIR_FLOAT_HIGH_CLAMP + stbir__simdf_min( e0, e0, high_clamp ); +#endif +#ifdef STBIR_FLOAT_LOW_CLAMP + stbir__simdf_max( e0, e0, low_clamp ); +#endif + stbir__encode_simdf4_unflip( e0 ); + stbir__simdf_store( output-4, e0 ); + output += 4; + encode += 4; + } + output -= 4; + #endif + + #else + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + float e; + STBIR_SIMD_NO_UNROLL(encode); + e = encode[ stbir__encode_order0 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[0-4] = e; + e = encode[ stbir__encode_order1 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[1-4] = e; + e = encode[ stbir__encode_order2 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[2-4] = e; + e = encode[ stbir__encode_order3 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[3-4] = e; + output += 4; + encode += 4; + } + output -= 4; + + #endif + + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + while( output < end_output ) + { + float e; + STBIR_NO_UNROLL(encode); + e = encode[ stbir__encode_order0 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[0] = e; + #if stbir__coder_min_num >= 2 + e = encode[ stbir__encode_order1 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[1] = e; + #endif + #if stbir__coder_min_num >= 3 + e = encode[ stbir__encode_order2 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[2] = e; + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif + + #endif +} + +#undef stbir__decode_suffix +#undef stbir__decode_simdf8_flip +#undef stbir__decode_simdf4_flip +#undef stbir__decode_order0 +#undef stbir__decode_order1 +#undef stbir__decode_order2 +#undef stbir__decode_order3 +#undef stbir__encode_order0 +#undef stbir__encode_order1 +#undef stbir__encode_order2 +#undef stbir__encode_order3 +#undef stbir__encode_simdf8_unflip +#undef stbir__encode_simdf4_unflip +#undef stbir__encode_simdfX_unflip +#undef STBIR__CODER_NAME +#undef stbir__coder_min_num +#undef stbir__decode_swizzle +#undef stbir_scalar_hi_clamp +#undef stbir_scalar_lo_clamp +#undef STB_IMAGE_RESIZE_DO_CODERS + +#elif defined( STB_IMAGE_RESIZE_DO_VERTICALS) + +#ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#define STBIR_chans( start, end ) STBIR_strs_join14(start,STBIR__vertical_channels,end,_cont) +#else +#define STBIR_chans( start, end ) STBIR_strs_join1(start,STBIR__vertical_channels,end) +#endif + +#if STBIR__vertical_channels >= 1 +#define stbIF0( code ) code +#else +#define stbIF0( code ) +#endif +#if STBIR__vertical_channels >= 2 +#define stbIF1( code ) code +#else +#define stbIF1( code ) +#endif +#if STBIR__vertical_channels >= 3 +#define stbIF2( code ) code +#else +#define stbIF2( code ) +#endif +#if STBIR__vertical_channels >= 4 +#define stbIF3( code ) code +#else +#define stbIF3( code ) +#endif +#if STBIR__vertical_channels >= 5 +#define stbIF4( code ) code +#else +#define stbIF4( code ) +#endif +#if STBIR__vertical_channels >= 6 +#define stbIF5( code ) code +#else +#define stbIF5( code ) +#endif +#if STBIR__vertical_channels >= 7 +#define stbIF6( code ) code +#else +#define stbIF6( code ) +#endif +#if STBIR__vertical_channels >= 8 +#define stbIF7( code ) code +#else +#define stbIF7( code ) +#endif + +static void STBIR_chans( stbir__vertical_scatter_with_,_coeffs)( float ** outputs, float const * vertical_coefficients, float const * input, float const * input_end ) +{ + stbIF0( float STBIR_SIMD_STREAMOUT_PTR( * ) output0 = outputs[0]; float c0s = vertical_coefficients[0]; ) + stbIF1( float STBIR_SIMD_STREAMOUT_PTR( * ) output1 = outputs[1]; float c1s = vertical_coefficients[1]; ) + stbIF2( float STBIR_SIMD_STREAMOUT_PTR( * ) output2 = outputs[2]; float c2s = vertical_coefficients[2]; ) + stbIF3( float STBIR_SIMD_STREAMOUT_PTR( * ) output3 = outputs[3]; float c3s = vertical_coefficients[3]; ) + stbIF4( float STBIR_SIMD_STREAMOUT_PTR( * ) output4 = outputs[4]; float c4s = vertical_coefficients[4]; ) + stbIF5( float STBIR_SIMD_STREAMOUT_PTR( * ) output5 = outputs[5]; float c5s = vertical_coefficients[5]; ) + stbIF6( float STBIR_SIMD_STREAMOUT_PTR( * ) output6 = outputs[6]; float c6s = vertical_coefficients[6]; ) + stbIF7( float STBIR_SIMD_STREAMOUT_PTR( * ) output7 = outputs[7]; float c7s = vertical_coefficients[7]; ) + + #ifdef STBIR_SIMD + { + stbIF0(stbir__simdfX c0 = stbir__simdf_frepX( c0s ); ) + stbIF1(stbir__simdfX c1 = stbir__simdf_frepX( c1s ); ) + stbIF2(stbir__simdfX c2 = stbir__simdf_frepX( c2s ); ) + stbIF3(stbir__simdfX c3 = stbir__simdf_frepX( c3s ); ) + stbIF4(stbir__simdfX c4 = stbir__simdf_frepX( c4s ); ) + stbIF5(stbir__simdfX c5 = stbir__simdf_frepX( c5s ); ) + stbIF6(stbir__simdfX c6 = stbir__simdf_frepX( c6s ); ) + stbIF7(stbir__simdfX c7 = stbir__simdf_frepX( c7s ); ) + while ( ( (char*)input_end - (char*) input ) >= (16*stbir__simdfX_float_count) ) + { + stbir__simdfX o0, o1, o2, o3, r0, r1, r2, r3; + STBIR_SIMD_NO_UNROLL(output0); + + stbir__simdfX_load( r0, input ); stbir__simdfX_load( r1, input+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input+(3*stbir__simdfX_float_count) ); + + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( stbir__simdfX_load( o0, output0 ); stbir__simdfX_load( o1, output0+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output0+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output0+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c0 ); stbir__simdfX_madd( o1, o1, r1, c0 ); stbir__simdfX_madd( o2, o2, r2, c0 ); stbir__simdfX_madd( o3, o3, r3, c0 ); + stbir__simdfX_store( output0, o0 ); stbir__simdfX_store( output0+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output0+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output0+(3*stbir__simdfX_float_count), o3 ); ) + stbIF1( stbir__simdfX_load( o0, output1 ); stbir__simdfX_load( o1, output1+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output1+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output1+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c1 ); stbir__simdfX_madd( o1, o1, r1, c1 ); stbir__simdfX_madd( o2, o2, r2, c1 ); stbir__simdfX_madd( o3, o3, r3, c1 ); + stbir__simdfX_store( output1, o0 ); stbir__simdfX_store( output1+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output1+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output1+(3*stbir__simdfX_float_count), o3 ); ) + stbIF2( stbir__simdfX_load( o0, output2 ); stbir__simdfX_load( o1, output2+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output2+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output2+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c2 ); stbir__simdfX_madd( o1, o1, r1, c2 ); stbir__simdfX_madd( o2, o2, r2, c2 ); stbir__simdfX_madd( o3, o3, r3, c2 ); + stbir__simdfX_store( output2, o0 ); stbir__simdfX_store( output2+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output2+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output2+(3*stbir__simdfX_float_count), o3 ); ) + stbIF3( stbir__simdfX_load( o0, output3 ); stbir__simdfX_load( o1, output3+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output3+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output3+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c3 ); stbir__simdfX_madd( o1, o1, r1, c3 ); stbir__simdfX_madd( o2, o2, r2, c3 ); stbir__simdfX_madd( o3, o3, r3, c3 ); + stbir__simdfX_store( output3, o0 ); stbir__simdfX_store( output3+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output3+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output3+(3*stbir__simdfX_float_count), o3 ); ) + stbIF4( stbir__simdfX_load( o0, output4 ); stbir__simdfX_load( o1, output4+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output4+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output4+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c4 ); stbir__simdfX_madd( o1, o1, r1, c4 ); stbir__simdfX_madd( o2, o2, r2, c4 ); stbir__simdfX_madd( o3, o3, r3, c4 ); + stbir__simdfX_store( output4, o0 ); stbir__simdfX_store( output4+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output4+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output4+(3*stbir__simdfX_float_count), o3 ); ) + stbIF5( stbir__simdfX_load( o0, output5 ); stbir__simdfX_load( o1, output5+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output5+(2*stbir__simdfX_float_count)); stbir__simdfX_load( o3, output5+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c5 ); stbir__simdfX_madd( o1, o1, r1, c5 ); stbir__simdfX_madd( o2, o2, r2, c5 ); stbir__simdfX_madd( o3, o3, r3, c5 ); + stbir__simdfX_store( output5, o0 ); stbir__simdfX_store( output5+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output5+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output5+(3*stbir__simdfX_float_count), o3 ); ) + stbIF6( stbir__simdfX_load( o0, output6 ); stbir__simdfX_load( o1, output6+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output6+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output6+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c6 ); stbir__simdfX_madd( o1, o1, r1, c6 ); stbir__simdfX_madd( o2, o2, r2, c6 ); stbir__simdfX_madd( o3, o3, r3, c6 ); + stbir__simdfX_store( output6, o0 ); stbir__simdfX_store( output6+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output6+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output6+(3*stbir__simdfX_float_count), o3 ); ) + stbIF7( stbir__simdfX_load( o0, output7 ); stbir__simdfX_load( o1, output7+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output7+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output7+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c7 ); stbir__simdfX_madd( o1, o1, r1, c7 ); stbir__simdfX_madd( o2, o2, r2, c7 ); stbir__simdfX_madd( o3, o3, r3, c7 ); + stbir__simdfX_store( output7, o0 ); stbir__simdfX_store( output7+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output7+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output7+(3*stbir__simdfX_float_count), o3 ); ) + #else + stbIF0( stbir__simdfX_mult( o0, r0, c0 ); stbir__simdfX_mult( o1, r1, c0 ); stbir__simdfX_mult( o2, r2, c0 ); stbir__simdfX_mult( o3, r3, c0 ); + stbir__simdfX_store( output0, o0 ); stbir__simdfX_store( output0+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output0+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output0+(3*stbir__simdfX_float_count), o3 ); ) + stbIF1( stbir__simdfX_mult( o0, r0, c1 ); stbir__simdfX_mult( o1, r1, c1 ); stbir__simdfX_mult( o2, r2, c1 ); stbir__simdfX_mult( o3, r3, c1 ); + stbir__simdfX_store( output1, o0 ); stbir__simdfX_store( output1+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output1+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output1+(3*stbir__simdfX_float_count), o3 ); ) + stbIF2( stbir__simdfX_mult( o0, r0, c2 ); stbir__simdfX_mult( o1, r1, c2 ); stbir__simdfX_mult( o2, r2, c2 ); stbir__simdfX_mult( o3, r3, c2 ); + stbir__simdfX_store( output2, o0 ); stbir__simdfX_store( output2+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output2+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output2+(3*stbir__simdfX_float_count), o3 ); ) + stbIF3( stbir__simdfX_mult( o0, r0, c3 ); stbir__simdfX_mult( o1, r1, c3 ); stbir__simdfX_mult( o2, r2, c3 ); stbir__simdfX_mult( o3, r3, c3 ); + stbir__simdfX_store( output3, o0 ); stbir__simdfX_store( output3+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output3+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output3+(3*stbir__simdfX_float_count), o3 ); ) + stbIF4( stbir__simdfX_mult( o0, r0, c4 ); stbir__simdfX_mult( o1, r1, c4 ); stbir__simdfX_mult( o2, r2, c4 ); stbir__simdfX_mult( o3, r3, c4 ); + stbir__simdfX_store( output4, o0 ); stbir__simdfX_store( output4+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output4+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output4+(3*stbir__simdfX_float_count), o3 ); ) + stbIF5( stbir__simdfX_mult( o0, r0, c5 ); stbir__simdfX_mult( o1, r1, c5 ); stbir__simdfX_mult( o2, r2, c5 ); stbir__simdfX_mult( o3, r3, c5 ); + stbir__simdfX_store( output5, o0 ); stbir__simdfX_store( output5+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output5+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output5+(3*stbir__simdfX_float_count), o3 ); ) + stbIF6( stbir__simdfX_mult( o0, r0, c6 ); stbir__simdfX_mult( o1, r1, c6 ); stbir__simdfX_mult( o2, r2, c6 ); stbir__simdfX_mult( o3, r3, c6 ); + stbir__simdfX_store( output6, o0 ); stbir__simdfX_store( output6+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output6+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output6+(3*stbir__simdfX_float_count), o3 ); ) + stbIF7( stbir__simdfX_mult( o0, r0, c7 ); stbir__simdfX_mult( o1, r1, c7 ); stbir__simdfX_mult( o2, r2, c7 ); stbir__simdfX_mult( o3, r3, c7 ); + stbir__simdfX_store( output7, o0 ); stbir__simdfX_store( output7+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output7+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output7+(3*stbir__simdfX_float_count), o3 ); ) + #endif + + input += (4*stbir__simdfX_float_count); + stbIF0( output0 += (4*stbir__simdfX_float_count); ) stbIF1( output1 += (4*stbir__simdfX_float_count); ) stbIF2( output2 += (4*stbir__simdfX_float_count); ) stbIF3( output3 += (4*stbir__simdfX_float_count); ) stbIF4( output4 += (4*stbir__simdfX_float_count); ) stbIF5( output5 += (4*stbir__simdfX_float_count); ) stbIF6( output6 += (4*stbir__simdfX_float_count); ) stbIF7( output7 += (4*stbir__simdfX_float_count); ) + } + while ( ( (char*)input_end - (char*) input ) >= 16 ) + { + stbir__simdf o0, r0; + STBIR_SIMD_NO_UNROLL(output0); + + stbir__simdf_load( r0, input ); + + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( stbir__simdf_load( o0, output0 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c0 ) ); stbir__simdf_store( output0, o0 ); ) + stbIF1( stbir__simdf_load( o0, output1 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c1 ) ); stbir__simdf_store( output1, o0 ); ) + stbIF2( stbir__simdf_load( o0, output2 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c2 ) ); stbir__simdf_store( output2, o0 ); ) + stbIF3( stbir__simdf_load( o0, output3 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c3 ) ); stbir__simdf_store( output3, o0 ); ) + stbIF4( stbir__simdf_load( o0, output4 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c4 ) ); stbir__simdf_store( output4, o0 ); ) + stbIF5( stbir__simdf_load( o0, output5 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c5 ) ); stbir__simdf_store( output5, o0 ); ) + stbIF6( stbir__simdf_load( o0, output6 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c6 ) ); stbir__simdf_store( output6, o0 ); ) + stbIF7( stbir__simdf_load( o0, output7 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c7 ) ); stbir__simdf_store( output7, o0 ); ) + #else + stbIF0( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c0 ) ); stbir__simdf_store( output0, o0 ); ) + stbIF1( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c1 ) ); stbir__simdf_store( output1, o0 ); ) + stbIF2( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c2 ) ); stbir__simdf_store( output2, o0 ); ) + stbIF3( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c3 ) ); stbir__simdf_store( output3, o0 ); ) + stbIF4( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c4 ) ); stbir__simdf_store( output4, o0 ); ) + stbIF5( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c5 ) ); stbir__simdf_store( output5, o0 ); ) + stbIF6( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c6 ) ); stbir__simdf_store( output6, o0 ); ) + stbIF7( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c7 ) ); stbir__simdf_store( output7, o0 ); ) + #endif + + input += 4; + stbIF0( output0 += 4; ) stbIF1( output1 += 4; ) stbIF2( output2 += 4; ) stbIF3( output3 += 4; ) stbIF4( output4 += 4; ) stbIF5( output5 += 4; ) stbIF6( output6 += 4; ) stbIF7( output7 += 4; ) + } + } + #else + while ( ( (char*)input_end - (char*) input ) >= 16 ) + { + float r0, r1, r2, r3; + STBIR_NO_UNROLL(input); + + r0 = input[0], r1 = input[1], r2 = input[2], r3 = input[3]; + + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( output0[0] += ( r0 * c0s ); output0[1] += ( r1 * c0s ); output0[2] += ( r2 * c0s ); output0[3] += ( r3 * c0s ); ) + stbIF1( output1[0] += ( r0 * c1s ); output1[1] += ( r1 * c1s ); output1[2] += ( r2 * c1s ); output1[3] += ( r3 * c1s ); ) + stbIF2( output2[0] += ( r0 * c2s ); output2[1] += ( r1 * c2s ); output2[2] += ( r2 * c2s ); output2[3] += ( r3 * c2s ); ) + stbIF3( output3[0] += ( r0 * c3s ); output3[1] += ( r1 * c3s ); output3[2] += ( r2 * c3s ); output3[3] += ( r3 * c3s ); ) + stbIF4( output4[0] += ( r0 * c4s ); output4[1] += ( r1 * c4s ); output4[2] += ( r2 * c4s ); output4[3] += ( r3 * c4s ); ) + stbIF5( output5[0] += ( r0 * c5s ); output5[1] += ( r1 * c5s ); output5[2] += ( r2 * c5s ); output5[3] += ( r3 * c5s ); ) + stbIF6( output6[0] += ( r0 * c6s ); output6[1] += ( r1 * c6s ); output6[2] += ( r2 * c6s ); output6[3] += ( r3 * c6s ); ) + stbIF7( output7[0] += ( r0 * c7s ); output7[1] += ( r1 * c7s ); output7[2] += ( r2 * c7s ); output7[3] += ( r3 * c7s ); ) + #else + stbIF0( output0[0] = ( r0 * c0s ); output0[1] = ( r1 * c0s ); output0[2] = ( r2 * c0s ); output0[3] = ( r3 * c0s ); ) + stbIF1( output1[0] = ( r0 * c1s ); output1[1] = ( r1 * c1s ); output1[2] = ( r2 * c1s ); output1[3] = ( r3 * c1s ); ) + stbIF2( output2[0] = ( r0 * c2s ); output2[1] = ( r1 * c2s ); output2[2] = ( r2 * c2s ); output2[3] = ( r3 * c2s ); ) + stbIF3( output3[0] = ( r0 * c3s ); output3[1] = ( r1 * c3s ); output3[2] = ( r2 * c3s ); output3[3] = ( r3 * c3s ); ) + stbIF4( output4[0] = ( r0 * c4s ); output4[1] = ( r1 * c4s ); output4[2] = ( r2 * c4s ); output4[3] = ( r3 * c4s ); ) + stbIF5( output5[0] = ( r0 * c5s ); output5[1] = ( r1 * c5s ); output5[2] = ( r2 * c5s ); output5[3] = ( r3 * c5s ); ) + stbIF6( output6[0] = ( r0 * c6s ); output6[1] = ( r1 * c6s ); output6[2] = ( r2 * c6s ); output6[3] = ( r3 * c6s ); ) + stbIF7( output7[0] = ( r0 * c7s ); output7[1] = ( r1 * c7s ); output7[2] = ( r2 * c7s ); output7[3] = ( r3 * c7s ); ) + #endif + + input += 4; + stbIF0( output0 += 4; ) stbIF1( output1 += 4; ) stbIF2( output2 += 4; ) stbIF3( output3 += 4; ) stbIF4( output4 += 4; ) stbIF5( output5 += 4; ) stbIF6( output6 += 4; ) stbIF7( output7 += 4; ) + } + #endif + while ( input < input_end ) + { + float r = input[0]; + STBIR_NO_UNROLL(output0); + + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( output0[0] += ( r * c0s ); ) + stbIF1( output1[0] += ( r * c1s ); ) + stbIF2( output2[0] += ( r * c2s ); ) + stbIF3( output3[0] += ( r * c3s ); ) + stbIF4( output4[0] += ( r * c4s ); ) + stbIF5( output5[0] += ( r * c5s ); ) + stbIF6( output6[0] += ( r * c6s ); ) + stbIF7( output7[0] += ( r * c7s ); ) + #else + stbIF0( output0[0] = ( r * c0s ); ) + stbIF1( output1[0] = ( r * c1s ); ) + stbIF2( output2[0] = ( r * c2s ); ) + stbIF3( output3[0] = ( r * c3s ); ) + stbIF4( output4[0] = ( r * c4s ); ) + stbIF5( output5[0] = ( r * c5s ); ) + stbIF6( output6[0] = ( r * c6s ); ) + stbIF7( output7[0] = ( r * c7s ); ) + #endif + + ++input; + stbIF0( ++output0; ) stbIF1( ++output1; ) stbIF2( ++output2; ) stbIF3( ++output3; ) stbIF4( ++output4; ) stbIF5( ++output5; ) stbIF6( ++output6; ) stbIF7( ++output7; ) + } +} + +static void STBIR_chans( stbir__vertical_gather_with_,_coeffs)( float * outputp, float const * vertical_coefficients, float const ** inputs, float const * input0_end ) +{ + float STBIR_SIMD_STREAMOUT_PTR( * ) output = outputp; + + stbIF0( float const * input0 = inputs[0]; float c0s = vertical_coefficients[0]; ) + stbIF1( float const * input1 = inputs[1]; float c1s = vertical_coefficients[1]; ) + stbIF2( float const * input2 = inputs[2]; float c2s = vertical_coefficients[2]; ) + stbIF3( float const * input3 = inputs[3]; float c3s = vertical_coefficients[3]; ) + stbIF4( float const * input4 = inputs[4]; float c4s = vertical_coefficients[4]; ) + stbIF5( float const * input5 = inputs[5]; float c5s = vertical_coefficients[5]; ) + stbIF6( float const * input6 = inputs[6]; float c6s = vertical_coefficients[6]; ) + stbIF7( float const * input7 = inputs[7]; float c7s = vertical_coefficients[7]; ) + +#if ( STBIR__vertical_channels == 1 ) && !defined(STB_IMAGE_RESIZE_VERTICAL_CONTINUE) + // check single channel one weight + if ( ( c0s >= (1.0f-0.000001f) ) && ( c0s <= (1.0f+0.000001f) ) ) + { + STBIR_MEMCPY( output, input0, (char*)input0_end - (char*)input0 ); + return; + } +#endif + + #ifdef STBIR_SIMD + { + stbIF0(stbir__simdfX c0 = stbir__simdf_frepX( c0s ); ) + stbIF1(stbir__simdfX c1 = stbir__simdf_frepX( c1s ); ) + stbIF2(stbir__simdfX c2 = stbir__simdf_frepX( c2s ); ) + stbIF3(stbir__simdfX c3 = stbir__simdf_frepX( c3s ); ) + stbIF4(stbir__simdfX c4 = stbir__simdf_frepX( c4s ); ) + stbIF5(stbir__simdfX c5 = stbir__simdf_frepX( c5s ); ) + stbIF6(stbir__simdfX c6 = stbir__simdf_frepX( c6s ); ) + stbIF7(stbir__simdfX c7 = stbir__simdf_frepX( c7s ); ) + + while ( ( (char*)input0_end - (char*) input0 ) >= (16*stbir__simdfX_float_count) ) + { + stbir__simdfX o0, o1, o2, o3, r0, r1, r2, r3; + STBIR_SIMD_NO_UNROLL(output); + + // prefetch four loop iterations ahead (doesn't affect much for small resizes, but helps with big ones) + stbIF0( stbir__prefetch( input0 + (16*stbir__simdfX_float_count) ); ) + stbIF1( stbir__prefetch( input1 + (16*stbir__simdfX_float_count) ); ) + stbIF2( stbir__prefetch( input2 + (16*stbir__simdfX_float_count) ); ) + stbIF3( stbir__prefetch( input3 + (16*stbir__simdfX_float_count) ); ) + stbIF4( stbir__prefetch( input4 + (16*stbir__simdfX_float_count) ); ) + stbIF5( stbir__prefetch( input5 + (16*stbir__simdfX_float_count) ); ) + stbIF6( stbir__prefetch( input6 + (16*stbir__simdfX_float_count) ); ) + stbIF7( stbir__prefetch( input7 + (16*stbir__simdfX_float_count) ); ) + + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( stbir__simdfX_load( o0, output ); stbir__simdfX_load( o1, output+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output+(3*stbir__simdfX_float_count) ); + stbir__simdfX_load( r0, input0 ); stbir__simdfX_load( r1, input0+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input0+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input0+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c0 ); stbir__simdfX_madd( o1, o1, r1, c0 ); stbir__simdfX_madd( o2, o2, r2, c0 ); stbir__simdfX_madd( o3, o3, r3, c0 ); ) + #else + stbIF0( stbir__simdfX_load( r0, input0 ); stbir__simdfX_load( r1, input0+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input0+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input0+(3*stbir__simdfX_float_count) ); + stbir__simdfX_mult( o0, r0, c0 ); stbir__simdfX_mult( o1, r1, c0 ); stbir__simdfX_mult( o2, r2, c0 ); stbir__simdfX_mult( o3, r3, c0 ); ) + #endif + + stbIF1( stbir__simdfX_load( r0, input1 ); stbir__simdfX_load( r1, input1+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input1+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input1+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c1 ); stbir__simdfX_madd( o1, o1, r1, c1 ); stbir__simdfX_madd( o2, o2, r2, c1 ); stbir__simdfX_madd( o3, o3, r3, c1 ); ) + stbIF2( stbir__simdfX_load( r0, input2 ); stbir__simdfX_load( r1, input2+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input2+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input2+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c2 ); stbir__simdfX_madd( o1, o1, r1, c2 ); stbir__simdfX_madd( o2, o2, r2, c2 ); stbir__simdfX_madd( o3, o3, r3, c2 ); ) + stbIF3( stbir__simdfX_load( r0, input3 ); stbir__simdfX_load( r1, input3+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input3+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input3+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c3 ); stbir__simdfX_madd( o1, o1, r1, c3 ); stbir__simdfX_madd( o2, o2, r2, c3 ); stbir__simdfX_madd( o3, o3, r3, c3 ); ) + stbIF4( stbir__simdfX_load( r0, input4 ); stbir__simdfX_load( r1, input4+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input4+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input4+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c4 ); stbir__simdfX_madd( o1, o1, r1, c4 ); stbir__simdfX_madd( o2, o2, r2, c4 ); stbir__simdfX_madd( o3, o3, r3, c4 ); ) + stbIF5( stbir__simdfX_load( r0, input5 ); stbir__simdfX_load( r1, input5+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input5+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input5+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c5 ); stbir__simdfX_madd( o1, o1, r1, c5 ); stbir__simdfX_madd( o2, o2, r2, c5 ); stbir__simdfX_madd( o3, o3, r3, c5 ); ) + stbIF6( stbir__simdfX_load( r0, input6 ); stbir__simdfX_load( r1, input6+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input6+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input6+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c6 ); stbir__simdfX_madd( o1, o1, r1, c6 ); stbir__simdfX_madd( o2, o2, r2, c6 ); stbir__simdfX_madd( o3, o3, r3, c6 ); ) + stbIF7( stbir__simdfX_load( r0, input7 ); stbir__simdfX_load( r1, input7+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input7+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input7+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c7 ); stbir__simdfX_madd( o1, o1, r1, c7 ); stbir__simdfX_madd( o2, o2, r2, c7 ); stbir__simdfX_madd( o3, o3, r3, c7 ); ) + + stbir__simdfX_store( output, o0 ); stbir__simdfX_store( output+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output+(3*stbir__simdfX_float_count), o3 ); + output += (4*stbir__simdfX_float_count); + stbIF0( input0 += (4*stbir__simdfX_float_count); ) stbIF1( input1 += (4*stbir__simdfX_float_count); ) stbIF2( input2 += (4*stbir__simdfX_float_count); ) stbIF3( input3 += (4*stbir__simdfX_float_count); ) stbIF4( input4 += (4*stbir__simdfX_float_count); ) stbIF5( input5 += (4*stbir__simdfX_float_count); ) stbIF6( input6 += (4*stbir__simdfX_float_count); ) stbIF7( input7 += (4*stbir__simdfX_float_count); ) + } + + while ( ( (char*)input0_end - (char*) input0 ) >= 16 ) + { + stbir__simdf o0, r0; + STBIR_SIMD_NO_UNROLL(output); + + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( stbir__simdf_load( o0, output ); stbir__simdf_load( r0, input0 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c0 ) ); ) + #else + stbIF0( stbir__simdf_load( r0, input0 ); stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c0 ) ); ) + #endif + stbIF1( stbir__simdf_load( r0, input1 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c1 ) ); ) + stbIF2( stbir__simdf_load( r0, input2 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c2 ) ); ) + stbIF3( stbir__simdf_load( r0, input3 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c3 ) ); ) + stbIF4( stbir__simdf_load( r0, input4 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c4 ) ); ) + stbIF5( stbir__simdf_load( r0, input5 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c5 ) ); ) + stbIF6( stbir__simdf_load( r0, input6 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c6 ) ); ) + stbIF7( stbir__simdf_load( r0, input7 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c7 ) ); ) + + stbir__simdf_store( output, o0 ); + output += 4; + stbIF0( input0 += 4; ) stbIF1( input1 += 4; ) stbIF2( input2 += 4; ) stbIF3( input3 += 4; ) stbIF4( input4 += 4; ) stbIF5( input5 += 4; ) stbIF6( input6 += 4; ) stbIF7( input7 += 4; ) + } + } + #else + while ( ( (char*)input0_end - (char*) input0 ) >= 16 ) + { + float o0, o1, o2, o3; + STBIR_NO_UNROLL(output); + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( o0 = output[0] + input0[0] * c0s; o1 = output[1] + input0[1] * c0s; o2 = output[2] + input0[2] * c0s; o3 = output[3] + input0[3] * c0s; ) + #else + stbIF0( o0 = input0[0] * c0s; o1 = input0[1] * c0s; o2 = input0[2] * c0s; o3 = input0[3] * c0s; ) + #endif + stbIF1( o0 += input1[0] * c1s; o1 += input1[1] * c1s; o2 += input1[2] * c1s; o3 += input1[3] * c1s; ) + stbIF2( o0 += input2[0] * c2s; o1 += input2[1] * c2s; o2 += input2[2] * c2s; o3 += input2[3] * c2s; ) + stbIF3( o0 += input3[0] * c3s; o1 += input3[1] * c3s; o2 += input3[2] * c3s; o3 += input3[3] * c3s; ) + stbIF4( o0 += input4[0] * c4s; o1 += input4[1] * c4s; o2 += input4[2] * c4s; o3 += input4[3] * c4s; ) + stbIF5( o0 += input5[0] * c5s; o1 += input5[1] * c5s; o2 += input5[2] * c5s; o3 += input5[3] * c5s; ) + stbIF6( o0 += input6[0] * c6s; o1 += input6[1] * c6s; o2 += input6[2] * c6s; o3 += input6[3] * c6s; ) + stbIF7( o0 += input7[0] * c7s; o1 += input7[1] * c7s; o2 += input7[2] * c7s; o3 += input7[3] * c7s; ) + output[0] = o0; output[1] = o1; output[2] = o2; output[3] = o3; + output += 4; + stbIF0( input0 += 4; ) stbIF1( input1 += 4; ) stbIF2( input2 += 4; ) stbIF3( input3 += 4; ) stbIF4( input4 += 4; ) stbIF5( input5 += 4; ) stbIF6( input6 += 4; ) stbIF7( input7 += 4; ) + } + #endif + while ( input0 < input0_end ) + { + float o0; + STBIR_NO_UNROLL(output); + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( o0 = output[0] + input0[0] * c0s; ) + #else + stbIF0( o0 = input0[0] * c0s; ) + #endif + stbIF1( o0 += input1[0] * c1s; ) + stbIF2( o0 += input2[0] * c2s; ) + stbIF3( o0 += input3[0] * c3s; ) + stbIF4( o0 += input4[0] * c4s; ) + stbIF5( o0 += input5[0] * c5s; ) + stbIF6( o0 += input6[0] * c6s; ) + stbIF7( o0 += input7[0] * c7s; ) + output[0] = o0; + ++output; + stbIF0( ++input0; ) stbIF1( ++input1; ) stbIF2( ++input2; ) stbIF3( ++input3; ) stbIF4( ++input4; ) stbIF5( ++input5; ) stbIF6( ++input6; ) stbIF7( ++input7; ) + } +} + +#undef stbIF0 +#undef stbIF1 +#undef stbIF2 +#undef stbIF3 +#undef stbIF4 +#undef stbIF5 +#undef stbIF6 +#undef stbIF7 +#undef STB_IMAGE_RESIZE_DO_VERTICALS +#undef STBIR__vertical_channels +#undef STB_IMAGE_RESIZE_DO_HORIZONTALS +#undef STBIR_strs_join24 +#undef STBIR_strs_join14 +#undef STBIR_chans +#ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#undef STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#endif + +#else // !STB_IMAGE_RESIZE_DO_VERTICALS + +#define STBIR_chans( start, end ) STBIR_strs_join1(start,STBIR__horizontal_channels,end) + +#ifndef stbir__2_coeff_only +#define stbir__2_coeff_only() \ + stbir__1_coeff_only(); \ + stbir__1_coeff_remnant(1); +#endif + +#ifndef stbir__2_coeff_remnant +#define stbir__2_coeff_remnant( ofs ) \ + stbir__1_coeff_remnant(ofs); \ + stbir__1_coeff_remnant((ofs)+1); +#endif + +#ifndef stbir__3_coeff_only +#define stbir__3_coeff_only() \ + stbir__2_coeff_only(); \ + stbir__1_coeff_remnant(2); +#endif + +#ifndef stbir__3_coeff_remnant +#define stbir__3_coeff_remnant( ofs ) \ + stbir__2_coeff_remnant(ofs); \ + stbir__1_coeff_remnant((ofs)+2); +#endif + +#ifndef stbir__3_coeff_setup +#define stbir__3_coeff_setup() +#endif + +#ifndef stbir__4_coeff_start +#define stbir__4_coeff_start() \ + stbir__2_coeff_only(); \ + stbir__2_coeff_remnant(2); +#endif + +#ifndef stbir__4_coeff_continue_from_4 +#define stbir__4_coeff_continue_from_4( ofs ) \ + stbir__2_coeff_remnant(ofs); \ + stbir__2_coeff_remnant((ofs)+2); +#endif + +#ifndef stbir__store_output_tiny +#define stbir__store_output_tiny stbir__store_output +#endif + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_1_coeff)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__1_coeff_only(); + stbir__store_output_tiny(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_2_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__2_coeff_only(); + stbir__store_output_tiny(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_3_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__3_coeff_only(); + stbir__store_output_tiny(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_4_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_5_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__1_coeff_remnant(4); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_6_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__2_coeff_remnant(4); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_7_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + stbir__3_coeff_setup(); + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + + stbir__4_coeff_start(); + stbir__3_coeff_remnant(4); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_8_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__4_coeff_continue_from_4(4); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_9_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__4_coeff_continue_from_4(4); + stbir__1_coeff_remnant(8); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_10_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__4_coeff_continue_from_4(4); + stbir__2_coeff_remnant(8); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_11_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + stbir__3_coeff_setup(); + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__4_coeff_continue_from_4(4); + stbir__3_coeff_remnant(8); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_12_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__4_coeff_continue_from_4(4); + stbir__4_coeff_continue_from_4(8); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_n_coeffs_mod0 )( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 4 + 3 ) >> 2; + float const * hc = horizontal_coefficients; + + stbir__4_coeff_start(); + do { + hc += 4; + decode += STBIR__horizontal_channels * 4; + stbir__4_coeff_continue_from_4( 0 ); + --n; + } while ( n > 0 ); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_n_coeffs_mod1 )( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 5 + 3 ) >> 2; + float const * hc = horizontal_coefficients; + + stbir__4_coeff_start(); + do { + hc += 4; + decode += STBIR__horizontal_channels * 4; + stbir__4_coeff_continue_from_4( 0 ); + --n; + } while ( n > 0 ); + stbir__1_coeff_remnant( 4 ); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_n_coeffs_mod2 )( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 6 + 3 ) >> 2; + float const * hc = horizontal_coefficients; + + stbir__4_coeff_start(); + do { + hc += 4; + decode += STBIR__horizontal_channels * 4; + stbir__4_coeff_continue_from_4( 0 ); + --n; + } while ( n > 0 ); + stbir__2_coeff_remnant( 4 ); + + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_n_coeffs_mod3 )( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + stbir__3_coeff_setup(); + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 7 + 3 ) >> 2; + float const * hc = horizontal_coefficients; + + stbir__4_coeff_start(); + do { + hc += 4; + decode += STBIR__horizontal_channels * 4; + stbir__4_coeff_continue_from_4( 0 ); + --n; + } while ( n > 0 ); + stbir__3_coeff_remnant( 4 ); + + stbir__store_output(); + } while ( output < output_end ); +} + +static stbir__horizontal_gather_channels_func * STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_funcs)[4]= +{ + STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod0), + STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod1), + STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod2), + STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod3), +}; + +static stbir__horizontal_gather_channels_func * STBIR_chans(stbir__horizontal_gather_,_channels_funcs)[12]= +{ + STBIR_chans(stbir__horizontal_gather_,_channels_with_1_coeff), + STBIR_chans(stbir__horizontal_gather_,_channels_with_2_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_3_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_4_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_5_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_6_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_7_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_8_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_9_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_10_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_11_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_12_coeffs), +}; + +#undef STBIR__horizontal_channels +#undef STB_IMAGE_RESIZE_DO_HORIZONTALS +#undef stbir__1_coeff_only +#undef stbir__1_coeff_remnant +#undef stbir__2_coeff_only +#undef stbir__2_coeff_remnant +#undef stbir__3_coeff_only +#undef stbir__3_coeff_remnant +#undef stbir__3_coeff_setup +#undef stbir__4_coeff_start +#undef stbir__4_coeff_continue_from_4 +#undef stbir__store_output +#undef stbir__store_output_tiny +#undef STBIR_chans + +#endif // HORIZONALS + +#undef STBIR_strs_join2 +#undef STBIR_strs_join1 + +#endif // STB_IMAGE_RESIZE_DO_HORIZONTALS/VERTICALS/CODERS + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/contrib/tinyusdz/tinyusdz_repo/src/external/stb_image_write.h b/contrib/tinyusdz/tinyusdz_repo/src/external/stb_image_write.h new file mode 100644 index 000000000..e4b32ed1b --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/external/stb_image_write.h @@ -0,0 +1,1724 @@ +/* stb_image_write - v1.16 - public domain - http://nothings.org/stb + writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 + no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio or a callback. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation; though providing a custom + zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. + This library is designed for source code compactness and simplicity, + not optimal image file size or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can #define STBIW_MEMMOVE() to replace memmove() + You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function + for PNG compression (instead of the builtin one), it must have the following signature: + unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); + The returned data will be freed with STBIW_FREE() (free() by default), + so it must be heap allocated with STBIW_MALLOC() (malloc() by default), + +UNICODE: + + If compiling for Windows and you wish to use Unicode filenames, compile + with + #define STBIW_WINDOWS_UTF8 + and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert + Windows wchar_t filenames to utf8. + +USAGE: + + There are five functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + + void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically + + There are also five equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can configure it with these global variables: + int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE + int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression + int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode + + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + PNG allows you to set the deflate compression level by setting the global + variable 'stbi_write_png_compression_level' (it defaults to 8). + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + + JPEG does ignore alpha channels in input data; quality is between 1 and 100. + Higher quality looks better but results in a bigger image. + JPEG baseline (no JPEG progressive). + +CREDITS: + + + Sean Barrett - PNG/BMP/TGA + Baldur Karlsson - HDR + Jean-Sebastien Guay - TGA monochrome + Tim Kelsey - misc enhancements + Alan Hickman - TGA RLE + Emmanuel Julien - initial file IO callback implementation + Jon Olick - original jo_jpeg.cpp code + Daniel Gibson - integrate JPEG, allow external zlib + Aarni Koskela - allow choosing PNG filter + + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + github:xeekworx + Cap Petschulat + Simon Rodriguez + Ivan Tikhonov + github:ignotion + Adam Schackart + Andrew Kensler + +LICENSE + + See end of file for license information. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#include + +// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' +#ifndef STBIWDEF +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#ifdef __cplusplus +#define STBIWDEF extern "C" +#else +#define STBIWDEF extern +#endif +#endif +#endif + +#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations +STBIWDEF int stbi_write_tga_with_rle; +STBIWDEF int stbi_write_png_compression_level; +STBIWDEF int stbi_write_force_png_filter; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); + +#ifdef STBIW_WINDOWS_UTF8 +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + +STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi_write_png_compression_level = 8; +static int stbi_write_tga_with_rle = 1; +static int stbi_write_force_png_filter = -1; +#else +int stbi_write_png_compression_level = 8; +int stbi_write_tga_with_rle = 1; +int stbi_write_force_png_filter = -1; +#endif + +static int stbi__flip_vertically_on_write = 0; + +STBIWDEF void stbi_flip_vertically_on_write(int flag) +{ + stbi__flip_vertically_on_write = flag; +} + +typedef struct +{ + stbi_write_func *func; + void *context; + unsigned char buffer[64]; + int buf_used; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) +#ifdef __cplusplus +#define STBIW_EXTERN extern "C" +#else +#define STBIW_EXTERN extern +#endif +STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); + +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbiw__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = stbiw__fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context,&x,1); + break; } + case '2': { int x = va_arg(v,int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x>>8); + s->func(s->context,b,2); + break; } + case '4': { stbiw_uint32 x = va_arg(v,int); + unsigned char b[4]; + b[0]=STBIW_UCHAR(x); + b[1]=STBIW_UCHAR(x>>8); + b[2]=STBIW_UCHAR(x>>16); + b[3]=STBIW_UCHAR(x>>24); + s->func(s->context,b,4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__write_flush(stbi__write_context *s) +{ + if (s->buf_used) { + s->func(s->context, &s->buffer, s->buf_used); + s->buf_used = 0; + } +} + +static void stbiw__putc(stbi__write_context *s, unsigned char c) +{ + s->func(s->context, &c, 1); +} + +static void stbiw__write1(stbi__write_context *s, unsigned char a) +{ + if ((size_t)s->buf_used + 1 > sizeof(s->buffer)) + stbiw__write_flush(s); + s->buffer[s->buf_used++] = a; +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) +{ + int n; + if ((size_t)s->buf_used + 3 > sizeof(s->buffer)) + stbiw__write_flush(s); + n = s->buf_used; + s->buf_used = n+3; + s->buffer[n+0] = a; + s->buffer[n+1] = b; + s->buffer[n+2] = c; +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + stbiw__write1(s, d[comp - 1]); + + switch (comp) { + case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case + case 1: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + stbiw__write1(s, d[0]); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + stbiw__write1(s, d[comp - 1]); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i,j, j_end; + + if (y <= 0) + return; + + if (stbi__flip_vertically_on_write) + vdir *= -1; + + if (vdir < 0) { + j_end = -1; j = y-1; + } else { + j_end = y; j = 0; + } + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + stbiw__write_flush(s); + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) +{ + if (comp != 4) { + // write RGB bitmap + int pad = (-x*3) & 3; + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header + } else { + // RGBA bitmaps need a v4 header + // use BI_BITFIELDS mode with 32bpp and alpha mask + // (straight BI_RGB with alpha mask doesn't work in most readers) + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *)data,1,0, + "11 4 22 4" "4 44 22 444444 4444 4 444 444 444 444", + 'B', 'M', 14+108+x*y*4, 0, 0, 14+108, // file header + 108, x,y, 1,32, 3,0,0,0,0,0, 0xff0000,0xff00,0xff,0xff000000u, 0, 0,0,0, 0,0,0, 0,0,0, 0,0,0); // bitmap V4 header + } +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //!STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp-1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i,j,k; + int jend, jdir; + + stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); + + if (stbi__flip_vertically_on_write) { + j = 0; + jend = y; + jdir = 1; + } else { + j = y-1; + jend = -1; + jdir = -1; + } + for (; j != jend; j += jdir) { + unsigned char *row = (unsigned char *) data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + stbiw__write1(s, header); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + stbiw__write1(s, header); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + stbiw__write_flush(s); + } + return 1; +} + +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +#ifndef STBI_WRITE_NO_STDIO + +static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length+128); + STBIW_ASSERT(length+128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header)-1); + +#ifdef __STDC_LIB_EXT1__ + len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#else + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#endif + s->func(s->context, buffer, len); + + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); + STBIW_FREE(scratch); + return 1; + } +} + +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *) data); +} + +STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +#ifndef STBIW_ZLIB_COMPRESS +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +#define stbiw__sbraw(a) ((int *) (void *) (a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +#endif // STBIW_ZLIB_COMPRESS + +STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ +#ifdef STBIW_ZLIB_COMPRESS + // user provided a zlib compress implementation, use that + return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); +#else // use builtin + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); + if (hash_table == NULL) + return NULL; + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1,1); // BFINAL = 1 + stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) { best=d; bestloc=hlist[j]; } + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int) (data+i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbiw__zlib_huff(j+257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0,1); + + for (i=0; i < stbiw__ZHASH; ++i) + (void) stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + // store uncompressed instead if compression was worse + if (stbiw__sbn(out) > data_len + 2 + ((data_len+32766)/32767)*5) { + stbiw__sbn(out) = 2; // truncate to DEFLATE 32K window and FLEVEL = 1 + for (j = 0; j < data_len;) { + int blocklen = data_len - j; + if (blocklen > 32767) blocklen = 32767; + stbiw__sbpush(out, data_len - j == blocklen); // BFINAL = ?, BTYPE = 0 -- no compression + stbiw__sbpush(out, STBIW_UCHAR(blocklen)); // LEN + stbiw__sbpush(out, STBIW_UCHAR(blocklen >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(~blocklen)); // NLEN + stbiw__sbpush(out, STBIW_UCHAR(~blocklen >> 8)); + memcpy(out+stbiw__sbn(out), data+j, blocklen); + stbiw__sbn(out) += blocklen; + j += blocklen; + } + } + + { + // compute adler32 on input + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; } + s1 %= 65521; s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *) stbiw__sbraw(out); +#endif // STBIW_ZLIB_COMPRESS +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) +{ +#ifdef STBIW_CRC32 + return STBIW_CRC32(buffer, len); +#else + static unsigned int crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + unsigned int crc = ~0u; + int i; + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +#endif +} + +#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len+4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +// @OPTIMIZE: provide an option that always forces left-predict or paeth predict +static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) +{ + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = (y != 0) ? mapping : firstmap; + int i; + int type = mymap[filter_type]; + unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); + int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + + if (type==0) { + memcpy(line_buffer, z, width*n); + return; + } + + // first loop isn't optimized since it's just one pixel + for (i = 0; i < n; ++i) { + switch (type) { + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + } + switch (type) { + case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; + case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; + case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; + case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } +} + +STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int force_filter = stbi_write_force_png_filter; + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int j,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + if (force_filter >= 5) { + force_filter = -1; + } + + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j=0; j < y; ++j) { + int filter_type; + if (force_filter > -1) { + filter_type = force_filter; + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); + } else { // Estimate the best filter by running through all of them: + int best_filter = 0, best_filter_val = 0x7fffffff, est, i; + for (filter_type = 0; filter_type < 5; filter_type++) { + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); + + // Estimate the entropy of the line using this filter; the less, the better. + est = 0; + for (i = 0; i < x*n; ++i) { + est += abs((signed char) line_buffer[i]); + } + if (est < best_filter_val) { + best_filter_val = est; + best_filter = filter_type; + } + } + if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); + filter_type = best_filter; + } + } + // when we get here, filter_type contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) filter_type; + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + STBIW_MEMMOVE(o,sig,8); o+= 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o,13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o,0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o,0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + + f = stbiw__fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) +{ + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + + +/* *************************************************************************** + * + * JPEG writer + * + * This is based on Jon Olick's jo_jpeg.cpp: + * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html + */ + +static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, + 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; + +static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { + int bitBuf = *bitBufP, bitCnt = *bitCntP; + bitCnt += bs[1]; + bitBuf |= bs[0] << (24 - bitCnt); + while(bitCnt >= 8) { + unsigned char c = (bitBuf >> 16) & 255; + stbiw__putc(s, c); + if(c == 255) { + stbiw__putc(s, 0); + } + bitBuf <<= 8; + bitCnt -= 8; + } + *bitBufP = bitBuf; + *bitCntP = bitCnt; +} + +static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { + float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; + float z1, z2, z3, z4, z5, z11, z13; + + float tmp0 = d0 + d7; + float tmp7 = d0 - d7; + float tmp1 = d1 + d6; + float tmp6 = d1 - d6; + float tmp2 = d2 + d5; + float tmp5 = d2 - d5; + float tmp3 = d3 + d4; + float tmp4 = d3 - d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + d0 = tmp10 + tmp11; // phase 3 + d4 = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + d2 = tmp13 + z1; // phase 5 + d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + z2 = tmp10 * 0.541196100f + z5; // c2-c6 + z4 = tmp12 * 1.306562965f + z5; // c2+c6 + z3 = tmp11 * 0.707106781f; // c4 + + z11 = tmp7 + z3; // phase 5 + z13 = tmp7 - z3; + + *d5p = z13 + z2; // phase 6 + *d3p = z13 - z2; + *d1p = z11 + z4; + *d7p = z11 - z4; + + *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; +} + +static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val-1 : val; + bits[1] = 1; + while(tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if(end0pos == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + return DU[0]; + } + for(i = 1; i <= end0pos; ++i) { + int startpos = i; + int nrzeroes; + unsigned short bits[2]; + for (; DU[i]==0 && i<=end0pos; ++i) { + } + nrzeroes = i-startpos; + if ( nrzeroes >= 16 ) { + int lng = nrzeroes>>4; + int nrmarker; + for (nrmarker=1; nrmarker <= lng; ++nrmarker) + stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + stbiw__jpg_calcBits(DU[i], bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + if(end0pos != 63) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; + static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; + static const unsigned char std_ac_luminance_values[] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; + static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; + static const unsigned char std_ac_chrominance_values[] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; + static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; + static const unsigned short YAC_HT[256][2] = { + {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const unsigned short UVAC_HT[256][2] = { + {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, + 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; + static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; + static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, + 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; + + int row, col, i, k, subsample; + float fdtbl_Y[64], fdtbl_UV[64]; + unsigned char YTable[64], UVTable[64]; + + if(!data || !width || !height || comp > 4 || comp < 1) { + return 0; + } + + quality = quality ? quality : 90; + subsample = quality <= 90 ? 1 : 0; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + for(i = 0; i < 64; ++i) { + int uvti, yti = (YQT[i]*quality+50)/100; + YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); + uvti = (UVQT[i]*quality+50)/100; + UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); + } + + for(row = 0, k = 0; row < 8; ++row) { + for(col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + { + static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; + static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; + const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), + 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; + s->func(s->context, (void*)head0, sizeof(head0)); + s->func(s->context, (void*)YTable, sizeof(YTable)); + stbiw__putc(s, 1); + s->func(s->context, UVTable, sizeof(UVTable)); + s->func(s->context, (void*)head1, sizeof(head1)); + s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); + s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); + stbiw__putc(s, 0x10); // HTYACinfo + s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); + s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); + stbiw__putc(s, 1); // HTUDCinfo + s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); + stbiw__putc(s, 0x11); // HTUACinfo + s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); + s->func(s->context, (void*)head2, sizeof(head2)); + } + + // Encode 8x8 macroblocks + { + static const unsigned short fillBits[] = {0x7F, 7}; + int DCY=0, DCU=0, DCV=0; + int bitBuf=0, bitCnt=0; + // comp == 2 is grey+alpha (alpha is ignored) + int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; + const unsigned char *dataR = (const unsigned char *)data; + const unsigned char *dataG = dataR + ofsG; + const unsigned char *dataB = dataR + ofsB; + int x, y, pos; + if(subsample) { + for(y = 0; y < height; y += 16) { + for(x = 0; x < width; x += 16) { + float Y[256], U[256], V[256]; + for(row = y, pos = 0; row < y+16; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+16; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + + // subsample U,V + { + float subU[64], subV[64]; + int yy, xx; + for(yy = 0, pos = 0; yy < 8; ++yy) { + for(xx = 0; xx < 8; ++xx, ++pos) { + int j = yy*32+xx*2; + subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f; + subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f; + } + } + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + } else { + for(y = 0; y < height; y += 8) { + for(x = 0; x < width; x += 8) { + float Y[64], U[64], V[64]; + for(row = y, pos = 0; row < y+8; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+8; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + + // Do the bit alignment of the EOI marker + stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); + } + + // EOI + stbiw__putc(s, 0xFF); + stbiw__putc(s, 0xD9); + + return 1; +} + +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); +} + + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.16 (2021-07-11) + make Deflate code emit uncompressed blocks when it would otherwise expand + support writing BMPs with alpha channel + 1.15 (2020-07-13) unknown + 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels + 1.13 + 1.12 + 1.11 (2019-08-11) + + 1.10 (2019-02-07) + support utf8 filenames in Windows; fix warnings and platform ifdefs + 1.09 (2018-02-11) + fix typo in zlib quality API, improve STB_I_W_STATIC in C++ + 1.08 (2018-01-29) + add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter + 1.07 (2017-07-24) + doc fix + 1.06 (2017-07-23) + writing JPEG (using Jon Olick's code) + 1.05 ??? + 1.04 (2017-03-03) + monochrome BMP expansion + 1.03 ??? + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/contrib/tinyusdz/tinyusdz_repo/src/handle-allocator.hh b/contrib/tinyusdz/tinyusdz_repo/src/handle-allocator.hh new file mode 100644 index 000000000..060d64e31 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/handle-allocator.hh @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022-Present Light Transport Entertainment Inc. +#pragma once + +#include +#include +//#include +//#include +#include +#include + +namespace tinyusdz { + +/// +/// Simple handle resource manager +/// Assume T is an unsigned integer type. +/// TODO(LTE): Allocate handle for a given value range. e.g. [minVal, maxVal) +/// +template +class HandleAllocator { +public: + // id = 0 is reserved. + HandleAllocator() : counter_(static_cast(1)){} + //~HandleAllocator(){} + + /// Allocates handle object. + bool Allocate(T *dst) { + + if (!dst) { + return false; + } + + T handle = 0; + + if (!freeList_.empty()) { + // Reuse last element. + handle = freeList_.back(); + freeList_.pop_back(); + // Delay sort until required + dirty_ = true; + (*dst) = handle; + return true; + } + + handle = counter_; + if ((handle >= static_cast(1)) && (handle < (std::numeric_limits::max)())) { + counter_++; + //std::cout << "conter = " << counter_ << "\n"; + (*dst) = handle; + return true; + } + + return false; + } + + /// Release handle object. + bool Release(const T handle) { + if (handle == counter_ - static_cast(1)) { + if (counter_ > static_cast(1)) { + counter_--; + } else { + return false; + } + } else { + if (handle >= static_cast(1)) { + freeList_.push_back(handle); + // Delay sort until required + dirty_ = true; + } else { + // invalid handle + return false; + } + } + + return true; + } + + bool Has(const T handle) const { + if (dirty_) { + std::sort(freeList_.begin(), freeList_.end()); + dirty_ = false; + } + + if (handle < 1) { + return false; + } + + // Do binary search. + if (std::binary_search(freeList_.begin(), freeList_.end(), handle)) { + return false; + } + + if (handle >= counter_) { + return false; + } + + return true; + } + + int64_t Size() const { + return counter_ - freeList_.size() - 1; + } + +private: + // TODO: Use unorderd_set or unorderd_map for efficiency? + // worst case complexity is still c.size() though. + mutable std::vector freeList_; // will be sorted in `Has` call. + T counter_{}; + mutable bool dirty_{true}; +}; + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/image-loader.cc b/contrib/tinyusdz/tinyusdz_repo/src/image-loader.cc new file mode 100644 index 000000000..81b1e62b0 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/image-loader.cc @@ -0,0 +1,466 @@ +// Support files +// +// - OpenEXR(through TinyEXR). 16bit and 32bit +// - TIFF/DNG(through TinyDNG). 8bit, 16bit and 32bit +// - PNG(8bit, 16bit), Jpeg, bmp, tga, ...(through stb_image or wuffs). +// +// TODO: +// +// - [ ] Use fpng for 8bit PNG when `stb_image` is used +// - [ ] 10bit, 12bit and 14bit DNG image +// - [ ] Support LoD tile, multi-channel for TIFF image +// + +#if defined(TINYUSDZ_WITH_EXR) +#include "external/tinyexr.h" +#endif + +#if defined(TINYUSDZ_USE_WUFFS_IMAGE_LOADER) + +#ifndef TINYUSDZ_NO_WUFFS_IMPLEMENTATION +#define WUFFS_IMPLEMENTATION + +#define WUFFS_CONFIG__MODULES +#define WUFFS_CONFIG__MODULE__BASE +#define WUFFS_CONFIG__MODULE__BMP +//#define WUFFS_CONFIG__MODULE__GIF +#define WUFFS_CONFIG__MODULE__PNG +#define WUFFS_CONFIG__MODULE__JPEG +//#define WUFFS_CONFIG__MODULE__WBMP +#endif + +#else + +#if !defined( TINYUSDZ_NO_BUILTIN_IMAGE_LOADER) + +// stb_image +#ifndef TINYUSDZ_NO_STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#endif + +#endif + +#endif + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#if defined(TINYUSDZ_USE_WUFFS_IMAGE_LOADER) + +#include "external/wuffs-unsupported-snapshot.c" + +#else + +#if !defined( TINYUSDZ_NO_BUILTIN_IMAGE_LOADER) + +// fpng, stb_image +#include "external/fpng.h" + +// avoid duplicated symbols when tinyusdz is linked to an app/library whose also use stb_image. +#define STB_IMAGE_STATIC +#include "external/stb_image.h" + +#endif + +#endif + +#if defined(TINYUSDZ_WITH_TIFF) +#ifndef TINYUSDZ_NO_TINY_DNG_LOADER_IMPLEMENTATION +#define TINY_DNG_LOADER_IMPLEMENTATION +#endif + +#ifndef TINY_DNG_NO_EXCEPTION +#define TINY_DNG_NO_EXCEPTION +#endif + +#ifndef TINY_DNG_LOADER_NO_STDIO +#define TINY_DNG_LOADER_NO_STDIO +#endif + +// Prevent including `stb_image.h` inside of tiny_dng_loader.h +#ifndef TINY_DNG_LOADER_NO_STB_IMAGE_INCLUDE +#define TINY_DNG_LOADER_NO_STB_IMAGE_INCLUDE +#endif + +#include "external/tiny_dng_loader.h" +#endif + + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#include "image-loader.hh" +#include "io-util.hh" + +namespace tinyusdz { +namespace image { + +namespace { + +#if defined(TINYUSDZ_USE_WUFFS_IMAGE_LOADER) + +bool DecodeImageWUFF(const uint8_t *bytes, const size_t size, + const std::string &uri, Image *image, std::string *warn, + std::string *err) { + + if (err) { + (*err) = "TODO: WUFF image loader.\n"; + } + + return false; + +} + +bool GetImageInfoWUFF(const uint8_t *bytes, const size_t size, + const std::string &uri, uint32_t *width, uint32_t *height, uint32_t *channels, std::string *warn, + std::string *err) { + if (err) { + (*err) = "TODO: WUFF image loader.\n"; + } + + return false; +} + + +#else + +#if !defined( TINYUSDZ_NO_BUILTIN_IMAGE_LOADER) + +// Decode image(png, jpg, ...) using STB +// 16bit PNG is supported. +bool DecodeImageSTB(const uint8_t *bytes, const size_t size, + const std::string &uri, Image *image, std::string *warn, + std::string *err) { + (void)warn; + + int w = 0, h = 0, comp = 0, req_comp = 0; + + unsigned char *data = nullptr; + + // force 32-bit textures for common Vulkan compatibility. It appears that + // some GPU drivers do not support 24-bit images for Vulkan + req_comp = 4; + int bits = 8; + + // It is possible that the image we want to load is a 16bit per channel image + // We are going to attempt to load it as 16bit per channel, and if it worked, + // set the image data accodingly. We are casting the returned pointer into + // unsigned char, because we are representing "bytes". But we are updating + // the Image metadata to signal that this image uses 2 bytes (16bits) per + // channel: + if (stbi_is_16_bit_from_memory(bytes, int(size))) { + data = reinterpret_cast( + stbi_load_16_from_memory(bytes, int(size), &w, &h, &comp, req_comp)); + if (data) { + bits = 16; + } + } + + // at this point, if data is still NULL, it means that the image wasn't + // 16bit per channel, we are going to load it as a normal 8bit per channel + // mage as we used to do: + // if image cannot be decoded, ignore parsing and keep it by its path + // don't break in this case + // FIXME we should only enter this function if the image is embedded. If + // `uri` references an image file, it should be left as it is. Image loading + // should not be mandatory (to support other formats) + if (!data) { + data = stbi_load_from_memory(bytes, int(size), &w, &h, &comp, req_comp); + } + + if (!data) { + // NOTE: you can use `warn` instead of `err` + if (err) { + (*err) += + "Unknown image format. STB cannot decode image data for image: " + + uri + "\".\n"; + } + return false; + } + + if ((w < 1) || (h < 1)) { + stbi_image_free(data); + if (err) { + (*err) += "Invalid image data for image: " + uri + "\"\n"; + } + return false; + } + + image->width = w; + image->height = h; + image->channels = req_comp; + image->bpp = bits; + image->format = Image::PixelFormat::UInt; + image->data.resize(size_t(w) * size_t(h) * size_t(req_comp) * size_t(bits / 8)); + std::copy(data, data + w * h * req_comp * (bits / 8), image->data.begin()); + stbi_image_free(data); + + return true; +} + +bool GetImageInfoSTB(const uint8_t *bytes, const size_t size, + const std::string &uri, uint32_t *width, uint32_t *height, uint32_t *channels, std::string *warn, + std::string *err) { + (void)warn; + (void)uri; + (void)err; // TODO + + int w = 0, h = 0, comp = 0; + + int ret = stbi_info_from_memory(bytes, int(size), &w, &h, &comp); + + if (w < 0) w = 0; + if (h < 0) h = 0; + if (comp < 0) comp = 0; + + if (ret == 1) { + if (width) { (*width) = uint32_t(w); } + if (height) { (*height) = uint32_t(h); } + if (channels) { (*channels) = uint32_t(comp); } + return true; + } + + return false; +} +#endif +#endif + +#if defined(TINYUSDZ_WITH_EXR) + +bool DecodeImageEXR(const uint8_t *bytes, const size_t size, + const std::string &uri, Image *image, + std::string *err) { + // TODO(syoyo): + // - [ ] Read fp16 image as fp16 + // - [ ] Read int16 image as int16 + // - [ ] Read int32 image as int32 + // - [ ] Multi-channel EXR + + float *rgba = nullptr; + int width; + int height; + const char *exrerr = nullptr; + // LoadEXRFromMemory always load EXR image as fp32 x RGBA + int ret = LoadEXRFromMemory(&rgba, &width, &height, bytes, size, &exrerr); + + if (exrerr) { + (*err) += std::string(exrerr); + + FreeEXRErrorMessage(exrerr); + } + + if (!ret) { + (*err) += "Failed to load EXR image: " + uri + "\n"; + return false; + } + + image->width = width; + image->height = height; + image->channels = 4; // RGBA + image->bpp = 32; // fp32 + image->format = Image::PixelFormat::Float; + image->data.resize(size_t(width) * size_t(height) * 4 * sizeof(float)); + memcpy(image->data.data(), rgba, sizeof(float) * size_t(width) * size_t(height) * 4); + + free(rgba); + + return true; +} + +#endif + +#if defined(TINYUSDZ_WITH_TIFF) + +bool DecodeImageTIFF(const uint8_t *bytes, const size_t size, + const std::string &uri, Image *image, + std::string *err) { + + + std::vector custom_fields; // no custom fields + std::vector images; + + std::string warn; + std::string dngerr; + + bool ret = tinydng::LoadDNGFromMemory(reinterpret_cast(bytes), uint32_t(size), custom_fields, &images, &warn, &dngerr); + + if (!dngerr.empty()) { + (*err) += dngerr; + } + + if (!ret) { + (*err) += "Failed to load TIFF/DNG image: " + uri + "\n"; + return false; + } + + // TODO(syoyo): Multi-layer TIFF + // Use the largest image(based on width pixels). + size_t largest = 0; + int largest_width = images[0].width; + for (size_t i = 1; i < images.size(); i++) { + if (largest_width < images[i].width) { + largest = i; + largest_width = images[i].width; + } + } + + size_t spp = size_t(images[largest].samples_per_pixel); + size_t bps = size_t(images[largest].bits_per_sample); + + if (spp > 4) { + (*err) += "Samples per pixel must be 0 ~ 4, but got " + std::to_string(spp) + " for image: " + uri + "\n"; + return false; + } + + // TODO: Support 10, 12 and 14bit Image(e.g. Apple ProRAW 12bit) + if ((bps == 8) || (bps == 16) || (bps == 32)) { + // ok + } else { + (*err) += "Invalid or unsupported bits per sample " + std::to_string(bps) + " for image: " + uri + "\n"; + return false; + } + + auto sample_format = images[largest].sample_format; + if (sample_format == tinydng::SAMPLEFORMAT_UINT) { + image->format = Image::PixelFormat::UInt; + } else if (sample_format == tinydng::SAMPLEFORMAT_INT) { + image->format = Image::PixelFormat::Int; + } else if (sample_format == tinydng::SAMPLEFORMAT_IEEEFP) { + image->format = Image::PixelFormat::Float; + } else { + (*err) += "Invalid Sample format for image: " + uri + "\n"; + return false; + } + + image->width = images[largest].width; + image->height = images[largest].height; + image->channels = int(spp); + image->bpp = int(bps); + + image->data.swap(images[largest].data); + + return true; +} + +#endif + +} // namespace + +nonstd::expected LoadImageFromMemory( + const uint8_t *addr, size_t sz, const std::string &uri) { + image::ImageResult ret; + std::string err; + +#if defined(TINYUSDZ_WITH_EXR) + if (TINYEXR_SUCCESS == IsEXRFromMemory(addr, sz)) { + + bool ok = DecodeImageEXR(addr, sz, uri, &ret.image, &err); + + if (!ok) { + return nonstd::make_unexpected(err); + } + + return std::move(ret); + } +#endif + +#if defined(TINYUSDZ_WITH_TIFF) + { + std::string msg; + if (tinydng::IsDNGFromMemory(reinterpret_cast(addr), uint32_t(sz), &msg)) { + + bool ok = DecodeImageTIFF(addr, sz, uri, &ret.image, &err); + + if (!ok) { + return nonstd::make_unexpected(err); + } + + return std::move(ret); + } + } +#endif + +#if defined(TINYUSDZ_USE_WUFFS_IMAGE_LOADER) + bool ok = DecodeImageWUFF(addr, sz, uri, &ret.image, &ret.warning, &err); +#elif !defined(TINYUSDZ_NO_BUILTIN_IMAGE_LOADER) + bool ok = DecodeImageSTB(addr, sz, uri, &ret.image, &ret.warning, &err); +#else + // TODO: Use user-supplied image loader + (void)addr; + (void)sz; + (void)uri; + bool ok = false; + err = "Image loading feature is disabled in this build. TODO: use user-supplied image loader\n"; +#endif + if (!ok) { + return nonstd::make_unexpected(err); + } + + return std::move(ret); +} + +nonstd::expected GetImageInfoFromMemory( + const uint8_t *addr, size_t sz, const std::string &uri) { + image::ImageInfoResult ret; + std::string err; + +#if defined(TINYUSDZ_WITH_EXR) + if (TINYEXR_SUCCESS == IsEXRFromMemory(addr, sz)) { + + return nonstd::make_unexpected("TODO: EXR format"); + } +#endif + +#if defined(TINYUSDZ_WITH_TIFF) + if (tinydng::IsDNGFromMemory(reinterpret_cast(addr), uint32_t(sz), &err)) { + + return nonstd::make_unexpected("TODO: TIFF/DNG format"); + + } +#endif + +#if defined(TINYUSDZ_USE_WUFFS_IMAGE_LOADER) + bool ok = GetImageInfoWUFF(addr, sz, uri, &ret.width, &ret.height, &ret.channels, &ret.warning, &err); +#elif !defined(TINYUSDZ_NO_BUILTIN_IMAGE_LOADER) + bool ok = GetImageInfoSTB(addr, sz, uri, &ret.width, &ret.height, &ret.channels, &ret.warning, &err); +#else + (void)addr; + (void)sz; + (void)uri; + bool ok = false; + err = "Image loading feature is disabled in this build. TODO: use user-supplied image info function\n"; +#endif + if (!ok) { + return nonstd::make_unexpected(err); + } + + return std::move(ret); +} + +nonstd::expected LoadImageFromFile( + const std::string &filename, const size_t max_memory_limit_in_mb) { + + // Assume filename is already resolved. + std::string filepath = filename; + + std::vector data; + size_t max_bytes = size_t(1024 * 1024 * max_memory_limit_in_mb); + std::string err; + if (!io::ReadWholeFile(&data, &err, filepath, max_bytes, + /* userdata */ nullptr)) { + return nonstd::make_unexpected("File not found or failed to read : \"" + filepath + "\"\n"); + } + + if (data.size() < 4) { + return nonstd::make_unexpected("File size too short. Looks like this file is not an image file : \"" + + filepath + "\"\n"); + } + + return LoadImageFromMemory(data.data(), data.size(), filename); +} + +} // namespace image +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/image-loader.hh b/contrib/tinyusdz/tinyusdz_repo/src/image-loader.hh new file mode 100644 index 000000000..d62a38010 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/image-loader.hh @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache 2.0 + +// Simple image loader +// supported file format: PNG(use fpng), BMP/JPEG(use stb_image), OpenEXR(use tinyexr), TIFF(use tinydng) +#pragma once + +#include +#include +#include + +#include "image-types.hh" + +#include "nonstd/expected.hpp" + +namespace tinyusdz { +namespace image { + +struct ImageResult { + Image image; + std::string warning; +}; + +struct ImageInfoResult { + uint32_t width; + uint32_t height; + uint32_t channels; + std::string warning; +}; + +/// +/// User-defined Image asset loader +/// +/// TOOD: Use FileFormat API? +/// + +/// +/// Callback function to load an image from memory. +/// +/// @param[in] addr Image data byte address. +/// @param[in] datasize Image data size in bytes. +/// @param[in] asset_name Corresponding asset/file name. +/// @param[inout] user_data User data pointer. Can be nullptr. +/// @param[out] warn Warning message. Can be nullptr. +/// @param[out] err Error message. Can be nullptr. +/// +/// @return true upon success. + +typedef bool (*LoadImageDataFunction)(ImageResult *image, const uint8_t *addr, const size_t datasize, const std::string &asset_name, void *user_data, std::string *warn, std::string *err); + +/// +/// Callback function to get info of an image from memory. +/// +/// @param[in] addr Image data byte address. +/// @param[in] datasize Image data size in bytes. +/// @param[in] asset_name Corresponding asset/file name. +/// @param[inout] user_data User data pointer. Can be nullptr. +/// @param[out] warn Warning message. Can be nullptr. +/// @param[out] err Error message. Can be nullptr. +/// +/// @return true upon success. + +typedef bool (*GetImageInfoFunction)(ImageInfoResult *image, const uint8_t *addr, const size_t datasize, const std::string &asset_name, void *user_data); + + +/// +/// Load image from a file. +/// +/// @param[in] filename Input filename(or URI) +/// @param[in] max_memory_limit_in_mb Optional. Maximum image file size in [MB]. Default = 1 TB. +/// @return ImageResult or error message(std::string) +/// +nonstd::expected LoadImageFromFile(const std::string &filename, const size_t max_memory_limit_in_mb = 1024*1024); + +/// +/// Get Image info from file. +/// +/// @param[in] filename Input filename(or URI) +/// @return ImageInfoResult or error message(std::string) +/// +nonstd::expected GetImageInfoFromFile(const std::string &filename); + +/// +/// Load image from memory +/// +/// @param[in] addr Memory address +/// @param[in] datasize Data size(in bytes) +/// @param[in] uri Input URI(or filename) as a hint. This is used only in error message. +/// @return ImageResult or error message(std::string) +/// +nonstd::expected LoadImageFromMemory(const uint8_t *addr, const size_t datasize, const std::string &uri); + +/// +/// Get Image info from a file. +/// +/// @param[in] addr Memory address +/// @param[in] datasize Data size(in bytes) +/// @param[in] uri Input URI(or filename) as a hint. This is used only in error message. +/// @return ImageResult or error message(std::string) +/// +nonstd::expected GetImageInfoFromMemory(const uint8_t *addr, const size_t datasize, const std::string &uri); + +} // namespace image +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/image-types.hh b/contrib/tinyusdz/tinyusdz_repo/src/image-types.hh new file mode 100644 index 000000000..d06380512 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/image-types.hh @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2024 - Present, Light Transport Entertainment Inc. +#pragma once + +#include +#include +#include + +namespace tinyusdz { + +// Simple image class. +// No colorspace conversion will be applied when decoding image data +// (e.g. from .jpg, .png). +struct Image { + // NOTE: Actual pixel value format is determined with combination of PixelFormat x bpp + // e.g. Float + 16 bpp = fp16 + enum class PixelFormat { + UInt, // LDR and HDR image + Int, // For ao/normal/displacement map, DNG photo + Float, // HDR image + }; + + std::string uri; // filename or uri; + + int width{-1}; // -1 = invalid + int height{-1}; // -1 = invalid + int channels{-1}; // Image channels. 3=RGB, 4=RGBA. -1 = invalid + int bpp{-1}; // bits per pixel. 8=LDR, 16,32=HDR + PixelFormat format{PixelFormat::UInt}; + + std::vector data; // Raw data. + + std::string colorspace; // Colorspace metadata in the image. Optional. +}; + +inline std::string to_string(Image::PixelFormat fmt) { + std::string s{"[[InvalidPixelFormat]]"}; + switch (fmt) { + case Image::PixelFormat::UInt: { s = "uint"; break; } + case Image::PixelFormat::Int: { s = "int"; break; } + case Image::PixelFormat::Float: { s = "float"; break; } + } + + return s; +} + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/image-util.cc b/contrib/tinyusdz/tinyusdz_repo/src/image-util.cc new file mode 100644 index 000000000..2cc4c8fe9 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/image-util.cc @@ -0,0 +1,794 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2023-Present, Light Transport Entertainment Inc. +// +// TODO +// - [ ] Optimize Rec.709 conversion +// +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#if !defined(TINYUSDZ_NO_STB_IMAGE_RESIZE_IMPLEMENTATION) +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#endif + +//#include "external/stb_image_resize.h" +#include "external/stb_image_resize2.h" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include "image-util.hh" +#include "value-types.hh" + +#if defined(TINYUSDZ_WITH_COLORIO) +#include "external/tiny-color-io.h" +#endif + +// From https://www.nayuki.io/page/srgb-transform-library -------------------- +/* + * sRGB transform (C++) + * + * Copyright (c) 2017 Project Nayuki. (MIT License) + * https://www.nayuki.io/page/srgb-transform-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +namespace SrgbTransform { + +/*---- sRGB values to linear intensities ----*/ +float srgbToLinear(float x); +double srgbToLinear(double x); +float linearToSrgb(float x); +double linearToSrgb(double x); +uint8_t linearToSrgb8bit(float x); +uint8_t linearToSrgb8bit(double x); + +float srgbToLinear(float x) { + if (x <= 0.0f) + return 0.0f; + else if (x >= 1.0f) + return 1.0f; + else if (x < 0.04045f) + return x / 12.92f; + else + return std::pow((x + 0.055f) / 1.055f, 2.4f); +} + + +double srgbToLinear(double x) { + if (x <= 0.0) + return 0.0; + else if (x >= 1.0) + return 1.0; + else if (x < 0.04045) + return x / 12.92; + else + return std::pow((x + 0.055) / 1.055, 2.4); +} + + +const float SRGB_8BIT_TO_LINEAR_FLOAT[1 << 8] = { + 0.0f, 3.03527e-4f, 6.07054e-4f, 9.10581e-4f, + 0.001214108f, 0.001517635f, 0.001821162f, 0.0021246888f, + 0.002428216f, 0.002731743f, 0.00303527f, 0.0033465358f, + 0.0036765074f, 0.004024717f, 0.004391442f, 0.0047769537f, + 0.005181517f, 0.005605392f, 0.0060488335f, 0.006512091f, + 0.0069954107f, 0.007499032f, 0.008023193f, 0.008568126f, + 0.009134059f, 0.009721218f, 0.010329823f, 0.010960095f, + 0.011612245f, 0.012286489f, 0.0129830325f, 0.013702083f, + 0.014443845f, 0.015208516f, 0.015996294f, 0.016807377f, + 0.017641956f, 0.018500222f, 0.019382363f, 0.020288564f, + 0.021219011f, 0.022173885f, 0.023153368f, 0.024157634f, + 0.025186861f, 0.026241222f, 0.027320893f, 0.02842604f, + 0.029556835f, 0.030713445f, 0.031896032f, 0.033104766f, + 0.034339808f, 0.035601314f, 0.036889452f, 0.038204372f, + 0.039546236f, 0.0409152f, 0.04231141f, 0.04373503f, + 0.045186203f, 0.046665087f, 0.048171826f, 0.049706567f, + 0.051269464f, 0.05286065f, 0.05448028f, 0.056128494f, + 0.057805438f, 0.059511244f, 0.06124606f, 0.06301002f, + 0.06480327f, 0.066625945f, 0.068478175f, 0.0703601f, + 0.07227185f, 0.07421357f, 0.07618539f, 0.07818743f, + 0.08021983f, 0.082282715f, 0.084376216f, 0.086500466f, + 0.08865559f, 0.09084172f, 0.093058966f, 0.09530747f, + 0.097587354f, 0.09989873f, 0.10224174f, 0.10461649f, + 0.107023105f, 0.10946172f, 0.111932434f, 0.11443538f, + 0.11697067f, 0.119538434f, 0.122138776f, 0.12477182f, + 0.12743768f, 0.13013647f, 0.13286832f, 0.13563333f, + 0.13843162f, 0.14126329f, 0.14412847f, 0.14702727f, + 0.14995979f, 0.15292616f, 0.15592647f, 0.15896083f, + 0.16202939f, 0.1651322f, 0.1682694f, 0.17144111f, + 0.1746474f, 0.17788842f, 0.18116425f, 0.18447499f, + 0.18782078f, 0.19120169f, 0.19461784f, 0.19806932f, + 0.20155625f, 0.20507874f, 0.20863687f, 0.21223076f, + 0.21586053f, 0.21952623f, 0.22322798f, 0.2269659f, + 0.23074007f, 0.23455061f, 0.2383976f, 0.24228115f, + 0.24620135f, 0.2501583f, 0.25415212f, 0.25818288f, + 0.2622507f, 0.26635563f, 0.27049783f, 0.27467734f, + 0.2788943f, 0.28314877f, 0.28744087f, 0.29177067f, + 0.2961383f, 0.3005438f, 0.30498734f, 0.30946895f, + 0.31398875f, 0.3185468f, 0.32314324f, 0.32777813f, + 0.33245155f, 0.33716366f, 0.34191445f, 0.3467041f, + 0.35153264f, 0.35640016f, 0.36130682f, 0.36625263f, + 0.3712377f, 0.37626216f, 0.38132605f, 0.38642946f, + 0.3915725f, 0.39675525f, 0.4019778f, 0.40724024f, + 0.41254264f, 0.4178851f, 0.4232677f, 0.42869052f, + 0.43415368f, 0.4396572f, 0.44520122f, 0.45078582f, + 0.45641103f, 0.46207702f, 0.4677838f, 0.4735315f, + 0.4793202f, 0.48514995f, 0.4910209f, 0.496933f, + 0.5028865f, 0.50888133f, 0.5149177f, 0.5209956f, + 0.52711517f, 0.53327644f, 0.5394795f, 0.5457245f, + 0.55201143f, 0.55834043f, 0.5647115f, 0.57112485f, + 0.57758045f, 0.58407843f, 0.59061885f, 0.5972018f, + 0.60382736f, 0.61049557f, 0.6172066f, 0.62396044f, + 0.63075715f, 0.6375969f, 0.6444797f, 0.65140563f, + 0.65837485f, 0.66538733f, 0.67244315f, 0.6795425f, + 0.6866853f, 0.6938718f, 0.7011019f, 0.7083758f, + 0.71569353f, 0.7230551f, 0.73046076f, 0.73791045f, + 0.74540424f, 0.7529422f, 0.7605245f, 0.76815116f, + 0.7758222f, 0.7835378f, 0.791298f, 0.7991027f, + 0.8069523f, 0.8148466f, 0.82278574f, 0.8307699f, + 0.838799f, 0.8468732f, 0.8549926f, 0.8631572f, + 0.8713671f, 0.8796224f, 0.8879231f, 0.8962694f, + 0.9046612f, 0.91309863f, 0.92158186f, 0.9301109f, + 0.9386857f, 0.9473065f, 0.9559733f, 0.9646863f, + 0.9734453f, 0.9822506f, 0.9911021f, 1.0f, +}; + + +const double SRGB_8BIT_TO_LINEAR_DOUBLE[1 << 8] = { + 0.0, 3.035269835488375e-4, 6.07053967097675e-4, 9.105809506465125e-4, + 0.00121410793419535, 0.0015176349177441874, 0.001821161901293025, 0.0021246888848418626, + 0.0024282158683907, 0.0027317428519395373, 0.003035269835488375, 0.003346535763899161, + 0.003676507324047436, 0.004024717018496307, 0.004391442037410293, 0.004776953480693729, + 0.005181516702338386, 0.005605391624202723, 0.006048833022857054, 0.006512090792594475, + 0.006995410187265387, 0.007499032043226175, 0.008023192985384994, 0.008568125618069307, + 0.009134058702220787, 0.00972121732023785, 0.010329823029626936, 0.010960094006488246, + 0.011612245179743885, 0.012286488356915872, 0.012983032342173012, 0.013702083047289686, + 0.014443843596092545, 0.01520851442291271, 0.01599629336550963, 0.016807375752887384, + 0.017641954488384078, 0.018500220128379697, 0.019382360956935723, 0.0202885630566524, + 0.021219010376003555, 0.022173884793387385, 0.02315336617811041, 0.024157632448504756, + 0.02518685962736163, 0.026241221894849898, 0.027320891639074894, 0.028426039504420793, + 0.0295568344378088, 0.030713443732993635, 0.03189603307301153, 0.033104766570885055, + 0.03433980680868217, 0.03560131487502034, 0.03688945040110004, 0.0382043715953465, + 0.03954623527673284, 0.04091519690685319, 0.042311410620809675, 0.043735029256973465, + 0.04518620438567554, 0.046665086336880095, 0.04817182422688942, 0.04970656598412723, + 0.05126945837404324, 0.052860647023180246, 0.05448027644244237, 0.05612849004960009, + 0.05780543019106723, 0.0595112381629812, 0.06124605423161761, 0.06301001765316767, + 0.06480326669290577, 0.06662593864377289, 0.06847816984440017, 0.07036009569659588, + 0.07227185068231748, 0.07421356838014963, 0.07618538148130785, 0.07818742180518633, + 0.08021982031446832, 0.0822827071298148, 0.08437621154414882, 0.08650046203654976, + 0.08865558628577294, 0.09084171118340768, 0.09305896284668745, 0.0953074666309647, + 0.09758734714186246, 0.09989872824711389, 0.10224173308810132, 0.10461648409110419, + 0.10702310297826761, 0.10946171077829933, 0.1119324278369056, 0.11443537382697373, + 0.11697066775851084, 0.11953842798834562, 0.12213877222960187, 0.12477181756095049, + 0.12743768043564743, 0.1301364766903643, 0.13286832155381798, 0.13563332965520566, + 0.13843161503245183, 0.14126329114027164, 0.14412847085805777, 0.14702726649759498, + 0.14995978981060856, 0.15292615199615017, 0.1559264637078274, 0.1589608350608804, + 0.162029375639111, 0.1651321945016676, 0.16826940018969075, 0.1714411007328226, + 0.17464740365558504, 0.17788841598362912, 0.18116424424986022, 0.184474994500441, + 0.18782077230067787, 0.19120168274079138, 0.1946178304415758, 0.19806931955994886, + 0.20155625379439707, 0.20507873639031693, 0.20863687014525575, 0.21223075741405523, + 0.21586050011389926, 0.2195261997292692, 0.2232279573168085, 0.22696587351009836, + 0.23074004852434915, 0.23455058216100522, 0.238397573812271, 0.24228112246555486, + 0.24620132670783548, 0.25015828472995344, 0.25415209433082675, 0.2581828529215958, + 0.26225065752969623, 0.26635560480286247, 0.2704977910130658, 0.27467731206038465, + 0.2788942634768104, 0.2831487404299921, 0.2874408377269175, 0.29177064981753587, + 0.2961382707983211, 0.3005437944157765, 0.3049873140698863, 0.30946892281750854, + 0.31398871337571754, 0.31854677812509186, 0.32314320911295075, 0.3277780980565422, + 0.33245153634617935, 0.33716361504833037, 0.3419144249086609, 0.3467040563550296, + 0.35153259950043936, 0.3564001441459435, 0.3613067797835095, 0.3662525955988395, + 0.3712376804741491, 0.3762621229909065, 0.38132601143253014, 0.386429433787049, + 0.39157247774972326, 0.39675523072562685, 0.4019777798321958, 0.4072402119017367, + 0.41254261348390375, 0.4178850708481375, 0.4232676699860717, 0.4286904966139066, + 0.43415363617474895, 0.4396571738409188, 0.44520119451622786, 0.45078578283822346, + 0.45641102318040466, 0.4620769996544071, 0.467783796112159, 0.47353149614800955, + 0.4793201831008268, 0.4851499400560704, 0.4910208498478356, 0.4969329950608704, + 0.5028864580325687, 0.5088813208549338, 0.5149176653765214, 0.5209955732043543, + 0.5271151257058131, 0.5332764040105052, 0.5394794890121072, 0.5457244613701866, + 0.5520114015120001, 0.5583403896342679, 0.5647115057049292, 0.5711248294648731, + 0.5775804404296506, 0.5840784178911641, 0.5906188409193369, 0.5972017883637634, + 0.6038273388553378, 0.6104955708078648, 0.6172065624196511, 0.6239603916750761, + 0.6307571363461468, 0.6375968739940326, 0.6444796819705821, 0.6514056374198242, + 0.6583748172794485, 0.665387298282272, 0.6724431569576875, 0.6795424696330938, + 0.6866853124353135, 0.6938717612919899, 0.7011018919329731, 0.7083757798916868, + 0.7156935005064807, 0.7230551289219693, 0.7304607400903537, 0.7379104087727308, + 0.7454042095403874, 0.7529422167760779, 0.7605245046752924, 0.768151147247507, + 0.7758222183174236, 0.7835377915261935, 0.7912979403326302, 0.799102738014409, + 0.8069522576692516, 0.8148465722161012, 0.8227857543962835, 0.8307698767746546, + 0.83879901174074, 0.846873231509858, 0.8549926081242338, 0.8631572134541023, + 0.8713671191987972, 0.8796223968878317, 0.8879231178819663, 0.8962693533742664, + 0.9046611743911496, 0.9130986517934192, 0.9215818562772946, 0.9301108583754237, + 0.938685728457888, 0.9473065367331999, 0.9559733532492861, 0.9646862478944651, + 0.9734452903984125, 0.9822505503331171, 0.9911020971138298, 1.0, +}; + + + +/*---- Linear intensities to sRGB values ----*/ + +float linearToSrgb(float x) { + if (x <= 0.0f) + return 0.0f; + else if (x >= 1.0f) + return 1.0f; + else if (x < 0.0031308f) + return x * 12.92f; + else + return std::pow(x, 1.0f / 2.4f) * 1.055f - 0.055f; +} + + +double linearToSrgb(double x) { + if (x <= 0.0) + return 0.0; + else if (x >= 1.0) + return 1.0; + else if (x < 0.0031308) + return x * 12.92; + else + return std::pow(x, 1.0 / 2.4) * 1.055 - 0.055; +} + +uint8_t linearToSrgb8bit(float x) { + if (x <= 0.0f) + return 0; + if (x >= 1.0f) + return 255; + const float *TABLE = SRGB_8BIT_TO_LINEAR_FLOAT; + int y = 0; + for (int i = 128; i != 0; i >>= 1) { + if (TABLE[y + i] <= x) + y += i; + } + if (x - TABLE[y] <= TABLE[y + 1] - x) + return static_cast((std::max)(0, (std::min)(255, y))); + else + return static_cast((std::max)(0, (std::min)(255, y + 1))); +} + +uint8_t linearToSrgb8bit(double x) { + if (x <= 0.0) + return 0; + if (x >= 1.0) + return 255; + const double *TABLE = SRGB_8BIT_TO_LINEAR_DOUBLE; + int y = 0; + for (int i = 128; i != 0; i >>= 1) { + if (TABLE[y + i] <= x) + y += i; + } + if (x - TABLE[y] <= TABLE[y + 1] - x) + return static_cast((std::max)(0, (std::min)(255, y))); + else + return static_cast((std::max)(0, (std::min)(255, y + 1))); +} + +// ---------------------------------------------------------------------------- + +} // SrgbTransform + +namespace tinyusdz { + +namespace detail { + +uint8_t f32_to_u8(float x); +uint8_t linearToRec709_8bit(float L); +float Rec709ToLinear(uint8_t v); + +uint8_t f32_to_u8(float x) { + return static_cast((std::max)(0, (std::min)(int(x * 255.0f), 255))); +} + +// Naiive implementation of Rec.709 +// +// https://en.wikipedia.org/wiki/Rec._709 + +uint8_t linearToRec709_8bit(float L) { + float V; + if (L > 1.0f) { + V = 1.0f; + } else if (L < 0.018f) { + V = 4.5f * L; + } else { + // 0.45 ~= 1/2.2 + V = 1.099f * std::pow(L, 0.45f) - 0.099f; + } + + return static_cast((std::max)(0, (std::min)(255, int(V)))); +} + +float Rec709ToLinear(uint8_t v) { + float V = v / 255.0f; + + float L; + if (V > 0.081f) { + L = V / 4.5f; + } else { + L = std::pow((V + 0.099f)/1.099f, (1.0f/0.45f)); + } + + return L; + +} + +} // namespace detail + +bool linear_f32_to_srgb_8bit(const std::vector &in_img, size_t width, + size_t height, + size_t channels, size_t channel_stride, + std::vector *out_img) { + + if ((width == 0) || + (height == 0) || + (channels == 0) || + (out_img == nullptr)) { + return false; + } + + if (channel_stride == 0) { + channel_stride = channels; + } else { + if (channel_stride < channels) { + return false; + } + } + + size_t dest_size = size_t(width) * size_t(height) * channel_stride; + if (dest_size > in_img.size()) { + return false; + } + + out_img->resize(dest_size); + + for (size_t y = 0; y < height; y++) { + for (size_t x = 0; x < width; x++) { + for (size_t c = 0; c < channels; c++) { + size_t idx = channel_stride * width * y + channel_stride * x + c; + (*out_img)[idx] = SrgbTransform::linearToSrgb8bit(in_img[idx]); + } + + // remainder(usually alpha channel) + // Apply linear conversion. + for (size_t c = channels; c < channel_stride; c++) { + size_t idx = channel_stride * width * y + channel_stride * x + c; + (*out_img)[idx] = detail::f32_to_u8(in_img[idx]); + } + } + } + + return true; +} + +bool srgb_8bit_to_linear_f32(const std::vector &in_img, size_t width, + size_t height, + size_t channels, size_t channel_stride, + std::vector *out_img) { + + if ((width == 0) || + (height == 0) || + (channels == 0) || + (out_img == nullptr)) { + return false; + } + + if (channel_stride == 0) { + channel_stride = channels; + } else { + if (channel_stride < channels) { + return false; + } + } + + size_t dest_size = size_t(width) * size_t(height) * channel_stride; + if (dest_size > in_img.size()) { + return false; + } + + out_img->resize(dest_size); + + // TODO: Use table approach for larger image size? + + for (size_t y = 0; y < height; y++) { + for (size_t x = 0; x < width; x++) { + for (size_t c = 0; c < channels; c++) { + size_t idx = channel_stride * width * y + channel_stride * x + c; + (*out_img)[idx] = SrgbTransform::srgbToLinear(float(in_img[idx]) / 255.0f); + } + + // remainder(usually alpha channel) + // Apply linear conversion. + for (size_t c = channels; c < channel_stride; c++) { + size_t idx = channel_stride * width * y + channel_stride * x + c; + (*out_img)[idx] = float(in_img[idx]) / 255.0f; + } + } + } + + return true; +} + +bool srgb_f32_to_linear_f32(const std::vector &in_img, size_t width, + size_t height, + size_t channels, size_t channel_stride, + std::vector *out_img, const float scale_factor, const float bias, const float alpha_scale_factor, const float alpha_bias) { + + if ((width == 0) || + (height == 0) || + (channels == 0) || + (out_img == nullptr)) { + return false; + } + + if (channel_stride == 0) { + channel_stride = channels; + } else { + if (channel_stride < channels) { + return false; + } + } + + size_t dest_size = size_t(width) * size_t(height) * channel_stride; + if (dest_size > in_img.size()) { + return false; + } + + out_img->resize(dest_size); + + // assume input is in [0.0, 1.0] + for (size_t y = 0; y < height; y++) { + for (size_t x = 0; x < width; x++) { + for (size_t c = 0; c < channels; c++) { + size_t idx = channel_stride * width * y + channel_stride * x + c; + float f = in_img[idx] * scale_factor + bias; + (*out_img)[idx] = SrgbTransform::srgbToLinear(f); + } + + // remainder(usually alpha channel) + // Apply linear conversion. + for (size_t c = channels; c < channel_stride; c++) { + size_t idx = channel_stride * width * y + channel_stride * x + c; + float f = in_img[idx] * alpha_scale_factor + alpha_bias; + (*out_img)[idx] = f; + } + } + } + + return true; +} + +bool srgb_8bit_to_linear_8bit(const std::vector &in_img, size_t width, + size_t height, + size_t channels, size_t channel_stride, + std::vector *out_img) { + + if ((width == 0) || + (height == 0) || + (channels == 0) || + (out_img == nullptr)) { + return false; + } + + if (channel_stride == 0) { + channel_stride = channels; + } else { + if (channel_stride < channels) { + return false; + } + } + + size_t dest_size = size_t(width) * size_t(height) * channel_stride; + if (dest_size > in_img.size()) { + return false; + } + + out_img->resize(dest_size); + + // TODO: Precompute table. + uint8_t linearlization_table[256]; + for (size_t u = 0; u < 256; u++) { + float f = float(u) / 255.0f; + linearlization_table[u] = detail::f32_to_u8(SrgbTransform::srgbToLinear(f)); + } + + for (size_t y = 0; y < height; y++) { + for (size_t x = 0; x < width; x++) { + for (size_t c = 0; c < channels; c++) { + size_t idx = channel_stride * width * y + channel_stride * x + c; + (*out_img)[idx] = linearlization_table[in_img[idx]]; + } + + // remainder(usually alpha channel) + // no op. + for (size_t c = channels; c < channel_stride; c++) { + size_t idx = channel_stride * width * y + channel_stride * x + c; + (*out_img)[idx] = in_img[idx]; + } + } + } + + return true; +} + +bool u8_to_f32_image(const std::vector &in_img, size_t width, + size_t height, + size_t channels, + std::vector *out_img) { + if ((width == 0) || + (height == 0) || + (channels == 0) || + (out_img == nullptr)) { + return false; + } + + size_t num_pixels = size_t(width) * size_t(height) * channels; + if (num_pixels > in_img.size()) { + return false; + } + + out_img->resize(num_pixels); + + for (size_t i = 0; i < num_pixels; i++) { + (*out_img)[i] = float(in_img[i]) / 255.0f; + } + + return true; +} + +bool f32_to_u8_image(const std::vector &in_img, size_t width, + size_t height, + size_t channels, + std::vector *out_img, float scale, float bias) { + if ((width == 0) || + (height == 0) || + (channels == 0) || + (out_img == nullptr)) { + return false; + } + + size_t num_pixels = size_t(width) * size_t(height) * channels; + if (num_pixels > in_img.size()) { + return false; + } + + out_img->resize(num_pixels); + + for (size_t i = 0; i < num_pixels; i++) { + float f = scale * in_img[i] + bias; + (*out_img)[i] = detail::f32_to_u8(f); + } + + return true; +} + +bool linear_displayp3_to_linear_sRGB(const std::vector &in_img, size_t width, + size_t height, size_t channels, + std::vector *out_img) { + + // http://endavid.com/index.php?entry=79 + // https://tech.metail.com/introduction-colour-spaces-dci-p3/ + + + if (!out_img) { + return false; + } + + if (channels > 4) { + return false; + } + + if ((channels != 3) && (channels != 4)) { + return false; + } + + if (in_img.size() != (width * height * channels)) { + return false; + } + + out_img->resize(in_img.size()); + + if (channels == 3) { + for (size_t y = 0; y < height; y++) { + for (size_t x = 0; x < width; x++) { + float r, g, b; + r = in_img[3 * (y * width + x) + 0]; + g = in_img[3 * (y * width + x) + 1]; + b = in_img[3 * (y * width + x) + 2]; + + float out_rgb[3]; + out_rgb[0] = 1.2249f * r - 0.2247f * g; + out_rgb[1] = -0.0420f * r + 1.0419f * g; + out_rgb[2] = -0.0197f * r - 0.0786f * g + 1.0979f * b; + + // clamp + out_rgb[0] = (out_rgb[0] < 0.0f) ? 0.0f : out_rgb[0]; + out_rgb[1] = (out_rgb[1] < 0.0f) ? 0.0f : out_rgb[1]; + out_rgb[2] = (out_rgb[2] < 0.0f) ? 0.0f : out_rgb[2]; + + (*out_img)[3 * (y * width + x) + 0] = out_rgb[0]; + (*out_img)[3 * (y * width + x) + 1] = out_rgb[1]; + (*out_img)[3 * (y * width + x) + 2] = out_rgb[2]; + } + } + + } else { // rgba + for (size_t y = 0; y < height; y++) { + for (size_t x = 0; x < width; x++) { + float r, g, b, a; + r = in_img[4 * (y * width + x) + 0]; + g = in_img[4 * (y * width + x) + 1]; + b = in_img[4 * (y * width + x) + 2]; + a = in_img[4 * (y * width + x) + 3]; + + float out_rgb[3]; + out_rgb[0] = 1.2249f * r - 0.2247f * g; + out_rgb[1] = -0.0420f * r + 1.0419f * g; + out_rgb[2] = -0.0197f * r - 0.0786f * g + 1.0979f * b; + + // clamp + out_rgb[0] = (out_rgb[0] < 0.0f) ? 0.0f : out_rgb[0]; + out_rgb[1] = (out_rgb[1] < 0.0f) ? 0.0f : out_rgb[1]; + out_rgb[2] = (out_rgb[2] < 0.0f) ? 0.0f : out_rgb[2]; + + (*out_img)[4 * (y * width + x) + 0] = out_rgb[0]; + (*out_img)[4 * (y * width + x) + 1] = out_rgb[1]; + (*out_img)[4 * (y * width + x) + 2] = out_rgb[2]; + (*out_img)[4 * (y * width + x) + 3] = a; + } + } + } + + return true; +} + +bool linear_sRGB_to_linear_displayp3(const std::vector &in_img, size_t width, + size_t height, size_t channels, + std::vector *out_img) { + + // http://endavid.com/index.php?entry=79 + // https://tech.metail.com/introduction-colour-spaces-dci-p3/ + + + if (!out_img) { + return false; + } + + if (channels > 4) { + return false; + } + + if ((channels != 3) && (channels != 4)) { + return false; + } + + if (in_img.size() != (width * height * channels)) { + return false; + } + + out_img->resize(in_img.size()); + + if (channels == 3) { + for (size_t y = 0; y < height; y++) { + for (size_t x = 0; x < width; x++) { + float r, g, b; + r = in_img[3 * (y * width + x) + 0]; + g = in_img[3 * (y * width + x) + 1]; + b = in_img[3 * (y * width + x) + 2]; + + float out_rgb[3]; + out_rgb[0] = 0.8225f * r + 0.1774f * g; + out_rgb[1] = 0.0332f * r + 0.9669f * g; + out_rgb[2] = 0.0171f * r + 0.0724f * g + 0.9108f * b; + + // clamp for just in case. + out_rgb[0] = (out_rgb[0] < 0.0f) ? 0.0f : out_rgb[0]; + out_rgb[1] = (out_rgb[1] < 0.0f) ? 0.0f : out_rgb[1]; + out_rgb[2] = (out_rgb[2] < 0.0f) ? 0.0f : out_rgb[2]; + + (*out_img)[3 * (y * width + x) + 0] = out_rgb[0]; + (*out_img)[3 * (y * width + x) + 1] = out_rgb[1]; + (*out_img)[3 * (y * width + x) + 2] = out_rgb[2]; + } + } + + } else { // rgba + for (size_t y = 0; y < height; y++) { + for (size_t x = 0; x < width; x++) { + float r, g, b, a; + r = in_img[4 * (y * width + x) + 0]; + g = in_img[4 * (y * width + x) + 1]; + b = in_img[4 * (y * width + x) + 2]; + a = in_img[4 * (y * width + x) + 3]; + + float out_rgb[3]; + out_rgb[0] = 0.8225f * r + 0.1774f * g; + out_rgb[1] = 0.0332f * r + 0.9669f * g; + out_rgb[2] = 0.0171f * r + 0.0724f * g + 0.9108f * b; + + // clamp for just in case. + out_rgb[0] = (out_rgb[0] < 0.0f) ? 0.0f : out_rgb[0]; + out_rgb[1] = (out_rgb[1] < 0.0f) ? 0.0f : out_rgb[1]; + out_rgb[2] = (out_rgb[2] < 0.0f) ? 0.0f : out_rgb[2]; + + (*out_img)[4 * (y * width + x) + 0] = out_rgb[0]; + (*out_img)[4 * (y * width + x) + 1] = out_rgb[1]; + (*out_img)[4 * (y * width + x) + 2] = out_rgb[2]; + (*out_img)[4 * (y * width + x) + 3] = a; + } + } + } + + return true; +} + +bool displayp3_f16_to_linear_f32(const std::vector &in_img, size_t width, + size_t height, + size_t channels, size_t channel_stride, + std::vector *out_img, const float scale_factor, const float bias, const float alpha_scale_factor, const float alpha_bias) { + + if ((width == 0) || + (height == 0) || + (channels == 0) || + (out_img == nullptr)) { + return false; + } + + if (channel_stride == 0) { + channel_stride = channels; + } else { + if (channel_stride < channels) { + return false; + } + } + + size_t dest_size = size_t(width) * size_t(height) * channel_stride; + if (dest_size > in_img.size()) { + return false; + } + + out_img->resize(dest_size); + + // assume input is in [0.0, 1.0] + for (size_t y = 0; y < height; y++) { + for (size_t x = 0; x < width; x++) { + for (size_t c = 0; c < channels; c++) { + size_t idx = channel_stride * width * y + channel_stride * x + c; + float in_val = value::half_to_float(in_img[idx]); + float f = in_val * scale_factor + bias; + (*out_img)[idx] = SrgbTransform::srgbToLinear(f); // Display P3 use the same transfer function with sRGB + } + + // remainder(usually alpha channel) + // Apply linear conversion. + for (size_t c = channels; c < channel_stride; c++) { + size_t idx = channel_stride * width * y + channel_stride * x + c; + float in_val = value::half_to_float(in_img[idx]); + float f = in_val * alpha_scale_factor + alpha_bias; + (*out_img)[idx] = f; + } + } + } + + return true; +} + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/image-util.hh b/contrib/tinyusdz/tinyusdz_repo/src/image-util.hh new file mode 100644 index 000000000..64fd5e34c --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/image-util.hh @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2023-Present, Light Transport Entertainment Inc. +// +// Image utilities. +// Currently sRGB color space conversion feature is provided. +// +// TODO +// - [ ] Image resize using stb_image_resize2 +// - [ ] OIIO 3D LUT support through tinycolorio +// +#pragma once + +#include +#include +#include + +namespace tinyusdz { + +// forward decl +namespace value { +struct half; +}; + +/// +/// [0, 255] => [0.0, 1.0] +/// +bool u8_to_f32_image(const std::vector &in_img, + size_t width, size_t height, size_t channels, std::vector *out_img); + +/// +/// Apply x' = `scale_factor * x + bias` +/// Then u8 value is calculated as `255 * max(0.0, min(1.0, x'))` +/// +bool f32_to_u8_image(const std::vector &in_img, + size_t width, size_t height, size_t channels, std::vector *out_img, + float scale_factor=1.0f, + float bias=0.0f); + +/// +/// Convert fp32 image in linear space to 8bit image in sRGB color space. +/// +/// @param[in] in_image Input image in linear color space. Image size = +/// [width_byte_stride/sizeof(float), height, channel_stride] +/// @param[in] width Width pixels +/// @param[in] height Height pixels +/// @param[in] chanels Pixel channels to apply conversion. must be less than or +/// equal to `channel_stride` +/// @param[in] chanel_stride channel stride. For example, channels=3 and +/// channel_stride=4 to convert RGB channel to sRGB but leave alpha channel +/// linear for RGBA image. +/// @param[out] out_image Image in sRGB colorspace. Image size is same with +/// `in_image` +/// +/// @return true upon success. false when any parameter is invalid. +bool linear_f32_to_srgb_8bit(const std::vector &in_img, size_t width, + size_t height, size_t channels, size_t channel_stride, + std::vector *out_img); + +/// +/// Convert 8bit image in sRGB to fp32 image in linear sRGB color space. +/// +/// @param[in] in_image Input image in sRGB color space. Image size = +/// [width_byte_stride, height, channel_stride] +/// @param[in] width Width pixels +/// @param[in] height Height pixels +/// @param[in] chanels Pixel channels to apply conversion. must be less than or +/// equal to `channel_stride` +/// @param[in] chanel_stride channel stride. For example, channels=3 and +/// channel_stride=4 to apply inverse sRGB convertion to RGB channel but apply +/// linear conversion to alpha channel for RGBA image. +/// @param[out] out_image Image in linear color space. Image size is same with +/// `in_image` +/// +/// @return true upon success. false when any parameter is invalid. +bool srgb_8bit_to_linear_f32(const std::vector &in_img, size_t width, + size_t height, size_t channels, size_t channel_stride, + std::vector *out_img); + +bool srgb_8bit_to_linear_8bit(const std::vector &in_img, size_t width, + size_t height, size_t channels, size_t channel_stride, + std::vector *out_img); + +// Input texel value is transformed as: x' = in_img * scale_factor + bias for RGB +// alpha' = in_img * alpha_scale_factor + alpha_bias for alpha channel. +bool srgb_f32_to_linear_f32(const std::vector &in_img, size_t width, + size_t height, size_t channels, size_t channel_stride, + std::vector *out_img, const float scale_factor = 1.0f, const float bias = 0.0f, const float alpha_scale_factor = 1.0f, const float alpha_bias = 0.0f); + +/// +/// Convert 8bit image in Rec.709 to fp32 image in linear color space. +/// +/// @param[in] in_img Input image in Rec.709 color space. Image size = +/// [width_byte_stride, height, channel_stride] +/// @param[in] width Width pixels +/// @param[in] width_byte_stride Width byte stride. 0 = Use `width` * +/// channel_stride +/// @param[in] height Height pixels +/// @param[in] chanels Pixel channels to apply conversion. must be less than or +/// equal to `channel_stride` +/// @param[in] chanel_stride channel stride. For example, channels=3 and +/// channel_stride=4 to apply inverse Rec.709 convertion to RGB channel but +/// apply linear conversion to alpha channel for RGBA image. +/// @param[out] out_image Image in linear color space. Image size is same with +/// `in_image` +/// +/// @return true upon success. false when any parameter is invalid. +bool rec709_8bit_to_linear_f32(const std::vector &in_img, size_t width, + size_t width_byte_stride, size_t height, + size_t channels, size_t channel_stride, + std::vector *out_img); + +/// +/// Convert fp16 image in Display P3(P3-D65) to fp32 image in linear Display P3 color space. +/// +/// The conversion is identical to sRGB -> linear sRGB, since Display P3 uses same gamma curve(transfer function) with sRGB. +/// +/// Input value is scaled by x' = x * scale + bias. +/// +/// @param[in] in_img Input image in Display P3 color space. Image size = +/// [width_byte_stride, height, channel_stride] +/// @param[in] width Width pixels +/// @param[in] height Height pixels +/// @param[in] chanels Pixel channels to apply conversion. must be less than or +/// equal to `channel_stride` +/// @param[in] chanel_stride channel stride. For example, channels=3 and +/// channel_stride=4 to apply inverse gamma correction to RGB channel but apply +/// linear conversion to alpha channel for RGBA image. +/// @param[out] out_img Image in linear Display P3 color space. Image size is same with +/// `in_image` +/// @param[in] scale texel scale factor(RGB) +/// @param[in] bias texel bias(RGB) +/// @param[in] alpha_scale texel scale factor(alpha) +/// @param[in] alpha_bias texel bias(alpha) +/// +/// @return true upon success. false when any parameter is invalid. +bool displayp3_f16_to_linear_f32(const std::vector &in_img, size_t width, + size_t height, size_t channels, size_t channel_stride, + std::vector *out_img, const float scale_factor = 1.0f, const float bias = 0.0f, const float alpha_scale_factor = 1.0f, const float alpha_bias = 0.0f); + +/// +/// Convert fp32 image in linear Display P3 color space to 10bit Display P3(10 bit for RGB, 2 bit for alpha, 32bit in total) +/// Apply gamma curve + quantize values. +/// +/// Input image must be Mono, LA(Luminance + Alpha), RGB or RGBA. +/// +/// Monochrome image is converted to RGB +/// +/// When alpha channel is supplied, alpha value in [0.0, 1.0] is quantized to 2bit(~0.25 = 0, ~0.5 = 1, ~0.75 = 2, 0.75+ = 3) +/// +/// @param[in] in_img Input image in linear Display P3 color space. +/// @param[in] width Width pixels +/// @param[in] height Height pixels +/// @param[in] channels 1 = mono, 2 = luminance(mono) + alpha, 3 = RGB, 4 = RGBA +/// @param[out] out_img Image in linear Display P3 color space. Image size is same with +/// `in_image` +bool linear_f32_to_displayp3_u10(const std::vector &in_img, size_t width, + size_t height, size_t channels, + std::vector *out_img); + +/// +/// Convert linear Display P3 color space to linear sRGB color space. +/// +/// Input image must be RGB or RGBA. +/// (TODO: Support Mono, Lumi/Alpha) +/// +/// When alpha channel is supplied, no conversion applied to alpha value. +/// +/// @param[in] in_img Input image in linear Display P3 color space. +/// @param[in] width Width pixels +/// @param[in] height Height pixels +/// @param[in] channels 3 = RGB, 4 = RGBA +/// @param[out] out_img Image in linear sRGB color space. Image size is same with +/// `in_image` +bool linear_displayp3_to_linear_sRGB(const std::vector &in_img, size_t width, + size_t height, size_t channels, + std::vector *out_img); + +/// +/// Convert linear sRGB color space to linear Display P3 color space. +/// +/// Input image must be RGB or RGBA. +/// (TODO: Support Mono, Lumi/Alpha) +/// +/// When alpha channel is supplied, no conversion applied to alpha value. +/// +/// @param[in] in_img Input image in linear sRGB color space. +/// @param[in] width Width pixels +/// @param[in] height Height pixels +/// @param[in] channels 3 = RGB, 4 = RGBA +/// @param[out] out_img Image in linear Display P3 color space. Image size is same with +/// `in_image` +bool linear_sRGB_to_linear_displayp3(const std::vector &in_img, size_t width, + size_t height, size_t channels, + std::vector *out_img); + +/// +/// Resize fp32 image in linear color space. +/// +/// @param[in] in_image Input image in linear color space. +/// @param[in] src_width Source image width pixels +/// @param[in] src_width_byte_stride Source image width byte stride. 0 = Use +/// `src_width` * channels +/// @param[in] src_height Source image height pixels +/// @param[in] dest_width Dest image width pixels +/// @param[in] dest_width_byte_stride Dest image width byte stride. 0 = Use +/// `src_width` * channels +/// @param[in] src_height Dest image height pixels +// +/// @param[in] chanels Pixel channels both src and dest image. +/// @param[out] dest_img Resized image in linear color space(memory is allocated +/// inside this function). +/// +/// @return true upon success. false when any parameter is invalid. +bool resize_image_f32(const std::vector &src_img, size_t src_width, + size_t src_width_byte_stride, size_t src_height, + + size_t dest_width, size_t dest_width_byte_stride, + size_t dest_height, + + size_t channels, std::vector *dest_img); + +/// +/// Resize uint8 image in sRGB color space. +/// +/// @param[in] in_image Input image in sRGB color space. +/// @param[in] src_width Source image width pixels +/// @param[in] src_width_byte_stride Source image width byte stride. 0 = Use +/// `src_width` * channels +/// @param[in] src_height Source image height pixels +/// @param[in] dest_width Dest image width pixels +/// @param[in] dest_width_byte_stride Dest image width byte stride. 0 = Use +/// `src_width` * channels +/// @param[in] src_height Dest image height pixels +// +/// @param[in] chanels Pixel channels both src and dest image. +/// @param[out] dest_img Resized image in sRGB color space(memory is allocated +/// inside this function). +/// +/// @return true upon success. false when any parameter is invalid. +bool resize_image_u8_srgb(const std::vector &src_img, size_t src_width, + size_t src_width_byte_stride, size_t src_height, + + size_t dest_width, size_t dest_width_byte_stride, + size_t dest_height, + + size_t channels, std::vector *dest_img); + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/image-writer.cc b/contrib/tinyusdz/tinyusdz_repo/src/image-writer.cc new file mode 100644 index 000000000..68e79f685 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/image-writer.cc @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: Apache 2.0 +#if defined(TINYUSDZ_WITH_EXR) +#include "external/tinyexr.h" +#endif + +#if defined(TINYUSDZ_WITH_TIFF) +#include "external/tiny_dng_writer.h" +#endif + +#ifndef TINYUSDZ_NO_STB_IMAGE_WRITE_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION +#endif + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#include "external/stb_image_write.h" +#include "external/fpng.h" + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#include "image-writer.hh" +#include "io-util.hh" +#include "str-util.hh" + +namespace tinyusdz { +namespace image { + +namespace { + +bool DetectFileFormatFromExtension(const std::string &_ext, tinyusdz::image::WriteImageFormat &format) { + + std::string ext = to_lower(_ext); + + if (ext == "bmp") { + format = tinyusdz::image::WriteImageFormat::BMP; + } else if (ext == "png") { + format = tinyusdz::image::WriteImageFormat::PNG; + } else if ((ext == "jpg") || (ext == "jpeg")) { + format = tinyusdz::image::WriteImageFormat::JPEG; + } else if ((ext == "tiff") || (ext == "tif")) { + format = tinyusdz::image::WriteImageFormat::TIFF; + } else if (ext == "dng") { + format = tinyusdz::image::WriteImageFormat::DNG; + } else if (ext == "exr") { + format = tinyusdz::image::WriteImageFormat::EXR; + } + + return false; +} + +} // namespace + +nonstd::expected WriteImageToFile( + const std::string &filename, const Image &image, + WriteOption option) { + + + tinyusdz::image::WriteImageFormat format = option.format; + + if (format == tinyusdz::image::WriteImageFormat::Autodetect) { + if (!DetectFileFormatFromExtension(io::GetFileExtension(filename), format)) { + return nonstd::make_unexpected("Failed to determine image file format from extension: " + filename); + } + } + + if ((format == tinyusdz::image::WriteImageFormat::BMP) || + (format == tinyusdz::image::WriteImageFormat::JPEG)) { + // Currently LDR only + if (image.bpp != 8) { + return nonstd::make_unexpected("8bit only for BMP/JPEG output."); + } + + } else if (format == tinyusdz::image::WriteImageFormat::EXR) { + + if (image.bpp == 8) { + return nonstd::make_unexpected("Invalid bit per pixel(8) for EXR output."); + } + + } else if (format == tinyusdz::image::WriteImageFormat::TIFF) { + + if (image.bpp == 8) { + + } else if (image.bpp == 16) { + + } else if (image.bpp == 32) { + + } else { + return nonstd::make_unexpected("Invalid bit per pixel for EXR output."); + } + + } else if (format == tinyusdz::image::WriteImageFormat::DNG) { + // 16bit only for DNG + if (image.bpp != 16) { + return nonstd::make_unexpected("Bit per pixel must be 16 for DNG output."); + } + } else { + // ??? + return nonstd::make_unexpected("Internal error in WriteImageToFile."); + } + + (void)image; + + return nonstd::make_unexpected("TODO: Implement WriteImageToFile"); +} + +/// +/// @param[in] image Image data +/// @param[in] option Image write option(optional) +/// @return Serialized image data +/// +nonstd::expected, std::string> WriteImageToMemory( + const Image &image, const WriteOption option) +{ + (void)image; + (void)option; + + // TODO: Autodetect format + if (option.format == tinyusdz::image::WriteImageFormat::Autodetect) { + return nonstd::make_unexpected("TODO: Autodetect image format."); + } + + return nonstd::make_unexpected("TODO: Implement WriteImageToFile"); +} + +} // namespace image +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/image-writer.hh b/contrib/tinyusdz/tinyusdz_repo/src/image-writer.hh new file mode 100644 index 000000000..037de6053 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/image-writer.hh @@ -0,0 +1,52 @@ +// Simple image writer +// supported file format: PNG(use fpng), JPEG(use stb_image), OpenEXR(use +// tinyexr), TIFF/DNG(use tinydng) +#pragma once + +#include +#include +#include + +#include "nonstd/expected.hpp" +#include "tinyusdz.hh" + +namespace tinyusdz { +namespace image { + +// +// Autodetect = determine file format using filename or Image's pixelformat + +// bpp. +// +enum class WriteImageFormat { Autodetect, BMP, PNG, JPEG, EXR, TIFF, DNG }; + +struct WriteOption { + WriteImageFormat format{WriteImageFormat::Autodetect}; + bool half{false}; // Use half float for EXR + + // When non-zero value is set, prefer this bitdepth than Image's bpp. + // Can specify 10, 12 and 14 for DNG when writing 16bit input image as 10, 12 and 14bit respectively. + int bitdepth{ + 0}; +}; + +/// +/// @param[in] filename Output filename +/// @param[in] image Image data +/// @param[in] option Image write option(optional) +/// +/// @return true upon success. or error message(std::string) when failed. +/// +nonstd::expected WriteImageToFile( + const std::string &filename, const Image &image, + WriteOption option = WriteOption()); + +/// +/// @param[in] image Image data +/// @param[in] option Image write option(optional) +/// @return Serialized image data +/// +nonstd::expected, std::string> WriteImageToMemory( + const Image &image, const WriteOption option = WriteOption()); + +} // namespace image +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/integerCoding.cpp b/contrib/tinyusdz/tinyusdz_repo/src/integerCoding.cpp new file mode 100644 index 000000000..9be55634d --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/integerCoding.cpp @@ -0,0 +1,516 @@ +// TODO(syoyo) Report fatal error + +// +// Copyright 2017 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "Apache License") +// with the following modification; you may not use this file except in +// compliance with the Apache License and the following modification to it: +// Section 6. Trademarks. is deleted and replaced with: +// +// 6. Trademarks. This License does not grant permission to use the trade +// names, trademarks, service marks, or product names of the Licensor +// and its affiliates, except as required to comply with Section 4(c) of +// the License and to reproduce the content of the NOTICE file. +// +// You may obtain a copy of the Apache License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the Apache License with the above modification is +// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the Apache License for the specific +// language governing permissions and limitations under the Apache License. +// +//#include "pxr/pxr.h" +//#include "pxr/base/tf/diagnostic.h" +//#include "pxr/base/tf/fastCompression.h" +//#include "pxr/usd/usd/integerCoding.h" +#include "lz4-compression.hh" +#include "integerCoding.h" + +#include +#include +#include +#include +#include + +//PXR_NAMESPACE_OPEN_SCOPE +namespace tinyusdz { + +/* + +These integer coding & compression routines are tailored for what are typically +lists of indexes into other tables. The binary "usdc" file format has lots of +these in its "structural sections" that define the object hierarchy. + +The basic idea is to take a contiguous list of 32-bit integers and encode them +in a buffer that is not only smaller, but also still quite compressible by a +general compression algorithm, and then compress that buffer to produce a final +result. Decompression proceeds by going in reverse. The general compressor is +LZ4 via TfFastCompression. The integer coding scheme implemented here is +described below. + +We encode a list of integers as follows. First we transform the input to +produce a new list of integers where each element is the difference between it +and the previous integer in the input sequence. This is the sequence we encode. +Next we find the most common value in the sequence and write it to the output. +Then we write 2-bit codes, one for each integer, classifying it. Finally we +write a variable length section of integer data. The decoder uses the 2-bit +codes to understand how to interpret this variable length data. + +Given a list of integers, say: + +input = [123, 124, 125, 100125, 100125, 100126, 100126] + +We encode as follows. First, we transform the list to be the list of +differences to the previous integer, or the integer itself for the first element +in the list (this can be considered a difference to 0) to get: + +input_diffs = [123, 1, 1, 100000, 0, 1, 0] + +Then we find the most commonly occurring value in this sequence, which is '1'. +We write this most commonly occurring value into the output stream. + +output = [int32(1)] + +Next we write two sections, first a fixed length section, 2-bit codes per +integer, followed by a variable length section of integer data. The two bit +code indicates what "kind" of integer we have: + +00: The most common value +01: 8-bit integer +10: 16-bit integer +11: 32-bit integer + +For our example this gives: + +input = [123, 124, 125, 100125, 100125, 100126, 10026] +output = [int32(1) 01 00 00 11 01 00 01 XX int8(123) int32(100000) int8(0) int8(0)] + +Where 'XX' represents unused bits in the last byte of the codes section to round +up to an even number of bytes. + +In this case the output size is 12 bytes compared to the original input which +was 28 bytes. In the best possible case the output is (asymptotically) 2 bits +per integer (6.25% the original size), in the worst possible case it is +(asymptotically) 34 bits per integer (106.25% the original size). + +*/ + +namespace { + +template +inline typename std::enable_if< + std::is_integral::value && + std::is_unsigned::value && + sizeof(Int) == 4, + int32_t>::type +_Signed(Int x) +{ + if (x <= static_cast(INT32_MAX)) + return static_cast(x); + + if (x >= static_cast(INT32_MIN)) + return static_cast(x - INT32_MIN) + INT32_MIN; + + //TF_FATAL_ERROR("Unsupported C++ integer representation"); + return 0; +} + +template +inline typename std::enable_if< + std::is_integral::value && + std::is_signed::value && + sizeof(Int) == 4, + int32_t>::type +_Signed(Int x) +{ + return x; +} + +template +inline typename std::enable_if< + std::is_integral::value && + std::is_unsigned::value && + sizeof(Int) == 8, + int64_t>::type +_Signed(Int x) +{ + if (x <= static_cast(INT64_MAX)) + return static_cast(x); + + if (x >= static_cast(INT64_MIN)) + return static_cast(x - INT64_MIN) + INT64_MIN; + + //TF_FATAL_ERROR("Unsupported C++ integer representation"); + return 0; +} + +template +inline typename std::enable_if< + std::is_integral::value && + std::is_signed::value && + sizeof(Int) == 8, + int64_t>::type +_Signed(Int x) +{ + return x; +} + +template +inline char *_WriteBits(char *p, T val) +{ + memcpy(p, &val, sizeof(val)); + return p + sizeof(val); +} + +template +inline T _ReadBits(char const *&p) +{ + T ret; + memcpy(&ret, p, sizeof(ret)); + p += sizeof(ret); + return ret; +} + +template +constexpr size_t +_GetEncodedBufferSize(size_t numInts) +{ + // Calculate encoded integer size. + return numInts ? + /* commonValue */ (sizeof(Int)) + + /* numCodesBytes */ ((numInts * 2 + 7) / 8) + + /* maxIntBytes */ (numInts * sizeof(Int)) + : 0; +} + +template +struct _SmallTypes +{ + typedef typename std::conditional< + sizeof(Int) == 4, int8_t, int16_t>::type SmallInt; + typedef typename std::conditional< + sizeof(Int) == 4, int16_t, int32_t>::type MediumInt; +}; + +template +void _EncodeNHelper( + Iterator &cur, + typename std::iterator_traits::value_type commonValue, + typename std::make_signed< + typename std::iterator_traits::value_type + >::type &prevVal, + char *&codesOut, + char *&vintsOut) { + + using Int = typename std::iterator_traits::value_type; + using SInt = typename std::make_signed::type; + using SmallInt = typename _SmallTypes::SmallInt; + using MediumInt = typename _SmallTypes::MediumInt; + + static_assert(1 <= N && N <= 4, ""); + + enum Code { Common, Small, Medium, Large }; + + auto getCode = [commonValue](SInt x) { + std::numeric_limits smallLimit; + std::numeric_limits mediumLimit; + if (x == _Signed(commonValue)) { return Common; } + if (x >= smallLimit.min() && x <= smallLimit.max()) { return Small; } + if (x >= mediumLimit.min() && x <= mediumLimit.max()) { return Medium; } + return Large; + }; + + uint8_t codeByte = 0; + for (int i = 0; i != N; ++i) { + SInt val = _Signed(*cur) - prevVal; + prevVal = _Signed(*cur++); + Code code = getCode(val); + codeByte |= (code << (2 * i)); + switch (code) { + default: + case Common: + break; + case Small: + vintsOut = _WriteBits(vintsOut, static_cast(val)); + break; + case Medium: + vintsOut = _WriteBits(vintsOut, static_cast(val)); + break; + case Large: + vintsOut = _WriteBits(vintsOut, _Signed(val)); + break; + }; + } + codesOut = _WriteBits(codesOut, codeByte); +} + +template +void _DecodeNHelper( + char const *&codesIn, + char const *&vintsIn, + typename std::iterator_traits::value_type commonValue, + typename std::make_signed< + typename std::iterator_traits::value_type + >::type &prevVal, + Iterator &output) +{ + using Int = typename std::iterator_traits::value_type; + using SInt = typename std::make_signed::type; + using SmallInt = typename _SmallTypes::SmallInt; + using MediumInt = typename _SmallTypes::MediumInt; + + enum Code { Common, Small, Medium, Large }; + + uint8_t codeByte = *codesIn++; + for (int i = 0; i != N; ++i) { + switch ((codeByte & (3 << (2 * i))) >> (2 * i)) { + default: + case Common: + prevVal += commonValue; + break; + case Small: + prevVal += _ReadBits(vintsIn); + break; + case Medium: + prevVal += _ReadBits(vintsIn); + break; + case Large: + prevVal += _ReadBits(vintsIn); + break; + } + *output++ = static_cast(prevVal); + } +} + +template +size_t +_EncodeIntegers(Int const *begin, size_t numInts, char *output) +{ + using SInt = typename std::make_signed::type; + + if (numInts == 0) + return 0; + + // First find the most common element value. + SInt commonValue = 0; + { + size_t commonCount = 0; + std::unordered_map counts; + SInt prevVal = 0; + for (Int const *cur = begin, *end = begin + numInts; + cur != end; ++cur) { + SInt val = _Signed(*cur) - prevVal; + const size_t count = ++counts[val]; + if (count > commonCount) { + commonValue = val; + commonCount = count; + } else if (count == commonCount && val > commonValue) { + // Take the largest common value in case of a tie -- this gives + // the biggest potential savings in the encoded stream. + commonValue = val; + } + prevVal = _Signed(*cur); + } + } + + // Now code the values. + + // Write most common value. + char *p = _WriteBits(output, commonValue); + char *codesOut = p; + char *vintsOut = p + (numInts * 2 + 7) / 8; + + Int const *cur = begin; + SInt prevVal = 0; + while (numInts >= 4) { + _EncodeNHelper<4>(cur, commonValue, prevVal, codesOut, vintsOut); + numInts -= 4; + } + switch (numInts) { + case 0: default: break; + case 1: _EncodeNHelper<1>(cur, commonValue, prevVal, codesOut, vintsOut); + break; + case 2: _EncodeNHelper<2>(cur, commonValue, prevVal, codesOut, vintsOut); + break; + case 3: _EncodeNHelper<3>(cur, commonValue, prevVal, codesOut, vintsOut); + break; + }; + + return vintsOut - output; +} + +template +size_t _DecodeIntegers(char const *data, size_t numInts, Int *result) +{ + using SInt = typename std::make_signed::type; + + auto commonValue = _ReadBits(data); + + size_t numCodesBytes = (numInts * 2 + 7) / 8; + char const *codesIn = data; + char const *vintsIn = data + numCodesBytes; + + SInt prevVal = 0; + auto intsLeft = numInts; + while (intsLeft >= 4) { + _DecodeNHelper<4>(codesIn, vintsIn, commonValue, prevVal, result); + intsLeft -= 4; + } + switch (intsLeft) { + case 0: default: break; + case 1: _DecodeNHelper<1>(codesIn, vintsIn, commonValue, prevVal, result); + break; + case 2: _DecodeNHelper<2>(codesIn, vintsIn, commonValue, prevVal, result); + break; + case 3: _DecodeNHelper<3>(codesIn, vintsIn, commonValue, prevVal, result); + break; + }; + + return numInts; +} + +template +size_t +_CompressIntegers(Int const *begin, size_t numInts, char *output, std::string *err) +{ + // Working space. + std::unique_ptr + encodeBuffer(new char[_GetEncodedBufferSize(numInts)]); + + // Encode first. + size_t encodedSize = _EncodeIntegers(begin, numInts, encodeBuffer.get()); + + // Then compress. + return LZ4Compression::CompressToBuffer( + encodeBuffer.get(), output, encodedSize, err); +} + +template +size_t _DecompressIntegers(char const *compressed, size_t compressedSize, + Int *ints, size_t numInts, std::string *err, char *workingSpace) +{ + // Working space. + size_t workingSpaceSize = + Usd_IntegerCompression::GetDecompressionWorkingSpaceSize(numInts); + std::unique_ptr tmpSpace; + if (!workingSpace) { + tmpSpace.reset(new char[workingSpaceSize]); + workingSpace = tmpSpace.get(); + } + + size_t decompSz = LZ4Compression::DecompressFromBuffer( + compressed, workingSpace, compressedSize, workingSpaceSize, err); + + if (decompSz == 0) + return 0; + + return _DecodeIntegers(workingSpace, numInts, ints); +} + + +} // anon + +//////////////////////////////////////////////////////////////////////// +// 32 bit. + +size_t +Usd_IntegerCompression::GetCompressedBufferSize(size_t numInts) +{ + return LZ4Compression::GetCompressedBufferSize( + _GetEncodedBufferSize(numInts)); +} + +size_t +Usd_IntegerCompression::GetDecompressionWorkingSpaceSize(size_t numInts) +{ + return _GetEncodedBufferSize(numInts); +} + +size_t +Usd_IntegerCompression::CompressToBuffer( + int32_t const *ints, size_t numInts, char *compressed, std::string *err) +{ + return _CompressIntegers(ints, numInts, compressed, err); +} + +size_t +Usd_IntegerCompression::CompressToBuffer( + uint32_t const *ints, size_t numInts, char *compressed, std::string *err) +{ + return _CompressIntegers(ints, numInts, compressed, err); +} + +size_t +Usd_IntegerCompression::DecompressFromBuffer( + char const *compressed, size_t compressedSize, + int32_t *ints, size_t numInts, std::string *err, char *workingSpace) +{ + return _DecompressIntegers(compressed, compressedSize, + ints, numInts, err, workingSpace); +} + +size_t +Usd_IntegerCompression::DecompressFromBuffer( + char const *compressed, size_t compressedSize, + uint32_t *ints, size_t numInts, std::string *err, char *workingSpace) +{ + return _DecompressIntegers(compressed, compressedSize, + ints, numInts, err, workingSpace); +} + +//////////////////////////////////////////////////////////////////////// +// 64 bit. + +size_t +Usd_IntegerCompression64::GetCompressedBufferSize(size_t numInts) +{ + return LZ4Compression::GetCompressedBufferSize( + _GetEncodedBufferSize(numInts)); +} + +size_t +Usd_IntegerCompression64::GetDecompressionWorkingSpaceSize(size_t numInts) +{ + return _GetEncodedBufferSize(numInts); +} + +size_t +Usd_IntegerCompression64::CompressToBuffer( + int64_t const *ints, size_t numInts, char *compressed, std::string *err) +{ + return _CompressIntegers(ints, numInts, compressed, err); +} + +size_t +Usd_IntegerCompression64::CompressToBuffer( + uint64_t const *ints, size_t numInts, char *compressed, std::string *err) +{ + return _CompressIntegers(ints, numInts, compressed, err); +} + +size_t +Usd_IntegerCompression64::DecompressFromBuffer( + char const *compressed, size_t compressedSize, + int64_t *ints, size_t numInts, std::string *err, char *workingSpace) +{ + return _DecompressIntegers(compressed, compressedSize, + ints, numInts, err, workingSpace); +} + +size_t +Usd_IntegerCompression64::DecompressFromBuffer( + char const *compressed, size_t compressedSize, + uint64_t *ints, size_t numInts, std::string *err, char *workingSpace) +{ + return _DecompressIntegers(compressed, compressedSize, + ints, numInts, err, workingSpace); +} + +//PXR_NAMESPACE_CLOSE_SCOPE + +} // namespace tinyusdz + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/integerCoding.h b/contrib/tinyusdz/tinyusdz_repo/src/integerCoding.h new file mode 100644 index 000000000..110d1c946 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/integerCoding.h @@ -0,0 +1,144 @@ +// +// Copyright 2017 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "Apache License") +// with the following modification; you may not use this file except in +// compliance with the Apache License and the following modification to it: +// Section 6. Trademarks. is deleted and replaced with: +// +// 6. Trademarks. This License does not grant permission to use the trade +// names, trademarks, service marks, or product names of the Licensor +// and its affiliates, except as required to comply with Section 4(c) of +// the License and to reproduce the content of the NOTICE file. +// +// You may obtain a copy of the Apache License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the Apache License with the above modification is +// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the Apache License for the specific +// language governing permissions and limitations under the Apache License. +// +#ifndef USD_INTEGERCODING_H +#define USD_INTEGERCODING_H + +//#include "pxr/pxr.h" +//#include "pxr/usd/usd/api.h" + +#include +#include + +#define USD_API +//PXR_NAMESPACE_OPEN_SCOPE +namespace tinyusdz { + +class Usd_IntegerCompression +{ +public: + // Return the max compression buffer size required for \p numInts 32-bit + // integers. + USD_API + static size_t GetCompressedBufferSize(size_t numInts); + + // Return the max decompression working space size required for \p numInts + // 32-bit integers. + USD_API + static size_t GetDecompressionWorkingSpaceSize(size_t numInts); + + // Compress \p numInts ints from \p ints to \p compressed. The + // \p compressed space must point to at least + // GetCompressedBufferSize(numInts) bytes. Return the actual number + // of bytes written to \p compressed. + USD_API + static size_t CompressToBuffer( + int32_t const *ints, size_t numInts, char *compressed, std::string *err); + + // Compress \p numInts ints from \p ints to \p compressed. The + // \p compressed space must point to at least + // GetCompressedBufferSize(numInts) bytes. Return the actual number + // of bytes written to \p compressed. + USD_API + static size_t CompressToBuffer( + uint32_t const *ints, size_t numInts, char *compressed, std::string *err); + + // Decompress \p compressedSize bytes from \p compressed to produce + // \p numInts 32-bit integers into \p ints. Clients may supply + // \p workingSpace to save allocations if several decompressions will be + // done but it isn't required. If supplied it must point to at least + // GetDecompressionWorkingSpaceSize(numInts) bytes. + USD_API + static size_t DecompressFromBuffer( + char const *compressed, size_t compressedSize, + int32_t *ints, size_t numInts, std::string *err, + char *workingSpace=nullptr); + + // Decompress \p compressedSize bytes from \p compressed to produce + // \p numInts 32-bit integers into \p ints. Clients may supply + // \p workingSpace to save allocations if several decompressions will be + // done but it isn't required. If supplied it must point to at least + // GetDecompressionWorkingSpaceSize(numInts) bytes. + USD_API + static size_t DecompressFromBuffer( + char const *compressed, size_t compressedSize, + uint32_t *ints, size_t numInts, std::string *err, + char *workingSpace=nullptr); +}; + +class Usd_IntegerCompression64 +{ +public: + // Return the max compression buffer size required for \p numInts 64-bit + // integers. + USD_API + static size_t GetCompressedBufferSize(size_t numInts); + + // Return the max decompression working space size required for \p numInts + // 64-bit integers. + USD_API + static size_t GetDecompressionWorkingSpaceSize(size_t numInts); + + // Compress \p numInts ints from \p ints to \p compressed. The + // \p compressed space must point to at least + // GetCompressedBufferSize(numInts) bytes. Return the actual number + // of bytes written to \p compressed. + USD_API + static size_t CompressToBuffer( + int64_t const *ints, size_t numInts, char *compressed, std::string *err); + + // Compress \p numInts ints from \p ints to \p compressed. The + // \p compressed space must point to at least + // GetCompressedBufferSize(numInts) bytes. Return the actual number + // of bytes written to \p compressed. + USD_API + static size_t CompressToBuffer( + uint64_t const *ints, size_t numInts, char *compressed, std::string *err); + + // Decompress \p compressedSize bytes from \p compressed to produce + // \p numInts 64-bit integers into \p ints. Clients may supply + // \p workingSpace to save allocations if several decompressions will be + // done but it isn't required. If supplied it must point to at least + // GetDecompressionWorkingSpaceSize(numInts) bytes. + USD_API + static size_t DecompressFromBuffer( + char const *compressed, size_t compressedSize, + int64_t *ints, size_t numInts, std::string *err, + char *workingSpace=nullptr); + + // Decompress \p compressedSize bytes from \p compressed to produce + // \p numInts 64-bit integers into \p ints. Clients may supply + // \p workingSpace to save allocations if several decompressions will be + // done but it isn't required. If supplied it must point to at least + // GetDecompressionWorkingSpaceSize(numInts) bytes. + USD_API + static size_t DecompressFromBuffer( + char const *compressed, size_t compressedSize, + uint64_t *ints, size_t numInts, std::string *err, + char *workingSpace=nullptr); +}; + +//PXR_NAMESPACE_CLOSE_SCOPE +} // namespace tinyusdz + +#endif // USD_INTEGERCODING_H diff --git a/contrib/tinyusdz/tinyusdz_repo/src/io-util.cc b/contrib/tinyusdz/tinyusdz_repo/src/io-util.cc new file mode 100644 index 000000000..20e3d45dc --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/io-util.cc @@ -0,0 +1,760 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +#include +#include + +#ifdef _WIN32 + +#ifdef _MSC_VER +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include // include API for expanding a file path +#include + +#ifndef TINYUSDZ_MMAP_SUPPORTED +#define TINYUSDZ_MMAP_SUPPORTED (1) +#endif + + +#ifdef _MSC_VER +#undef NOMINMAX +#endif + +#undef WIN32_LEAN_AND_MEAN + +#if defined(__GLIBCXX__) // mingw + +#include // _O_RDONLY + +#include // fstream (all sorts of IO stuff) + stdio_filebuf (=streambuf) + +#endif // __GLIBCXX__ + +#else // !_WIN32 + +#if defined(TINYUSDZ_BUILD_IOS) || defined(TARGET_OS_IPHONE) || \ + defined(TARGET_IPHONE_SIMULATOR) || defined(__ANDROID__) || \ + defined(__EMSCRIPTEN__) || defined(__wasi__) + +// non posix + +// TODO: Add mmmap or similar feature support to these system. + +#else + +// Assume Posix +#include + +#include +#include + +#ifndef TINYUSDZ_MMAP_SUPPORTED +#define TINYUSDZ_MMAP_SUPPORTED (1) +#endif + + +#endif + +#endif // _WIN32 + +#ifndef TINYUSDZ_MMAP_SUPPORTED +#define TINYUSDZ_MMAP_SUPPORTED (0) +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#if !defined(__wasi__) +#include "external/filesystem/include/ghc/filesystem.hpp" +#include "external/glob/single_include/glob/glob.hpp" +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include "io-util.hh" +#include "str-util.hh" + +namespace tinyusdz { +namespace io { + +#ifdef TINYUSDZ_ANDROID_LOAD_FROM_ASSETS +AAssetManager *asset_manager = nullptr; +#endif + + +bool IsMMapSupported() { +#if TINYUSDZ_MMAP_SUPPORTED + return true; +#else + return false; +#endif +} + +bool MMapFile(const std::string &filepath, MMapFileHandle *handle, bool writable) { + (void)filepath; + (void)handle; + (void)writable; + +#if TINYUSDZ_MMAP_SUPPORTED +#if defined(_WIN32) +#if 0 // TODO + int fd = open(filepath.c_str(), writable ? O_RDWR : O_RDONLY); + HANDLE hFile = _get_ofhandle(fd); + HANDLE hMapping = CreateFileMapping(hFile, nullptr, PAGE_READWRITE, 0, 0, nullptr); + if (hMapping == nullptr) { + return false; + } + void *data = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0); + if (!data) { + return false; + } + CloseHandle(hMapping); +#else + return false; +#endif +#else // !WIN32 + // assume posix + FILE *fp = fopen(filepath.c_str(), writable ? "rw" : "r"); + int ret = std::fseek(fp, 0, SEEK_END); + if (ret != 0) { + fclose(fp); + return false; + } + + size_t size = size_t(std::ftell(fp)); + std::fseek(fp, 0, SEEK_SET); + + if (size == 0) { + return false; + } + + int fd = fileno(fp); + + int flags = MAP_PRIVATE; // delayed access + void *addr = mmap(nullptr, size, writable ? PROT_READ|PROT_WRITE : PROT_READ, flags, fd, 0); + if (addr == MAP_FAILED) { + return false; + } + + handle->addr = reinterpret_cast(addr); + handle->size = size; + handle->writable = writable; + handle->filename = filepath; + close(fd); + + return true; +#endif // !WIN32 +#else // !TINYUSDZ_MMAP_SUPPORTED + return false; +#endif +} + +bool UnmapFile(const MMapFileHandle &handle) { + (void)handle; +#if TINYUSDZ_MMAP_SUPPORTED +#if defined(_WIN32) + // TODO + return false; +#else // !WIN32 + if (handle.addr && handle.size) { + int ret = munmap(reinterpret_cast(handle.addr), handle.size); + // ignore return code for now + (void)ret; + return true; + } + return false; +#endif +#else // !TINYUSDZ_MMAP_SUPPORTED + return false; +#endif +} + + +std::string ExpandFilePath(const std::string &_filepath, void *) { + std::string filepath = _filepath; + if (filepath.size() > 2048) { + // file path too large. + // TODO: Report warn. + filepath.resize(2048); + } + +#ifdef _WIN32 + // Assume input `filepath` is encoded in UTF-8 + std::wstring wfilepath = UTF8ToWchar(filepath); + DWORD wlen = ExpandEnvironmentStringsW(wfilepath.c_str(), nullptr, 0); + wchar_t *wstr = new wchar_t[wlen]; + ExpandEnvironmentStringsW(wfilepath.c_str(), wstr, wlen); + + std::wstring ws(wstr); + delete[] wstr; + return WcharToUTF8(ws); + +#else + +#if defined(TINYUSDZ_BUILD_IOS) || defined(TARGET_OS_IPHONE) || \ + defined(TARGET_IPHONE_SIMULATOR) || defined(__ANDROID__) || \ + defined(__EMSCRIPTEN__) || defined(__OpenBSD__) || defined(__wasi__) + // no expansion + std::string s = filepath; +#else + std::string s; + wordexp_t p; + + if (filepath.empty()) { + return ""; + } + + // Quote the string to keep any spaces in filepath intact. + std::string quoted_path = "\"" + filepath + "\""; + // char** w; + // TODO: wordexp() is a awful API. Implement our own file path expansion + // routine. Set NOCMD for security. + int ret = wordexp(quoted_path.c_str(), &p, WRDE_NOCMD); + if (ret) { + // err + s = filepath; + return s; + } + + // Use first element only. + if (p.we_wordv) { + s = std::string(p.we_wordv[0]); + wordfree(&p); + } else { + s = filepath; + } + +#endif + + return s; +#endif +} + +#ifdef _WIN32 +std::wstring UTF8ToWchar(const std::string &str) { + int wstr_size = + MultiByteToWideChar(CP_UTF8, 0, str.data(), int(str.size()), nullptr, 0); + std::wstring wstr(size_t(wstr_size), 0); + MultiByteToWideChar(CP_UTF8, 0, str.data(), int(str.size()), &wstr[0], + int(wstr.size())); + return wstr; +} + +std::string WcharToUTF8(const std::wstring &wstr) { + int str_size = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), int(wstr.size()), + nullptr, 0, nullptr, nullptr); + std::string str(size_t(str_size), 0); + WideCharToMultiByte(CP_UTF8, 0, wstr.data(), int(wstr.size()), &str[0], + int(str.size()), nullptr, nullptr); + return str; +} +#endif + +bool ReadWholeFile(std::vector *out, std::string *err, + const std::string &filepath, size_t filesize_max, + void *userdata) { + (void)userdata; + +#ifdef TINYUSDZ_ANDROID_LOAD_FROM_ASSETS + if (tinyusdz::io::asset_manager) { + AAsset *asset = AAssetManager_open(asset_manager, filepath.c_str(), + AASSET_MODE_STREAMING); + if (!asset) { + if (err) { + (*err) += "File open error(from AssestManager) : " + filepath + "\n"; + } + return false; + } + size_t size = AAsset_getLength(asset); + if (size == 0) { + if (err) { + (*err) += "Invalid file size : " + filepath + + " (does the path point to a directory?)"; + } + return false; + } + out->resize(size); + AAsset_read(asset, reinterpret_cast(&out->at(0)), size); + AAsset_close(asset); + return true; + } else { + if (err) { + (*err) += "No asset manager specified : " + filepath + "\n"; + } + return false; + } + +#else +#ifdef _WIN32 +#if defined(__GLIBCXX__) // mingw + int file_descriptor = + _wopen(UTF8ToWchar(filepath).c_str(), _O_RDONLY | _O_BINARY); + __gnu_cxx::stdio_filebuf wfile_buf(file_descriptor, std::ios_base::in); + std::istream f(&wfile_buf); +#elif defined(_MSC_VER) || defined(_LIBCPP_VERSION) + // For libcxx, assume _LIBCPP_HAS_OPEN_WITH_WCHAR is defined to accept + // `wchar_t *` + std::ifstream f(UTF8ToWchar(filepath).c_str(), std::ifstream::binary); +#else + // Unknown compiler/runtime + std::ifstream f(filepath.c_str(), std::ifstream::binary); +#endif +#else + std::ifstream f(filepath.c_str(), std::ifstream::binary); +#endif + if (!f) { + if (err) { + (*err) += "File open error : " + filepath + "\n"; + } + return false; + } + + // For directory(and pipe?), peek() will fail(Posix gnustl/libc++ only) + int buf = f.peek(); + (void)buf; + if (!f) { + if (err) { + (*err) += "File read error. Maybe empty file or invalid file : " + filepath + "\n"; + } + return false; + } + + f.seekg(0, f.end); + size_t sz = static_cast(f.tellg()); + f.seekg(0, f.beg); + + if (int64_t(sz) < 0) { + if (err) { + (*err) += "Invalid file size : " + filepath + + " (does the path point to a directory?)"; + } + return false; + } else if (sz == 0) { + if (err) { + (*err) += "File is empty : " + filepath + "\n"; + } + return false; + } else if (uint64_t(sz) >= uint64_t(std::numeric_limits::max())) { + // Posixish environment. + if (err) { + (*err) += "Invalid File(Pipe or special device?) : " + filepath + "\n"; + } + return false; + } + + if ((filesize_max > 0) && (sz > filesize_max)) { + if (err) { + (*err) += "File size is too large : " + filepath + + " sz = " + std::to_string(sz) + + ", allowed max filesize = " + std::to_string(filesize_max) + + "\n"; + } + return false; + } + + out->resize(sz); + f.read(reinterpret_cast(&out->at(0)), + static_cast(sz)); + + if (!f) { + // read failure. + if (err) { + (*err) += "Failed to read file: " + filepath + "\n"; + } + return false; + } + + return true; +#endif +} + +bool ReadFileHeader(std::vector *out, std::string *err, + const std::string &filepath, uint32_t max_read_bytes, + void *userdata) { + (void)userdata; + + // hard limit to 1MB. + max_read_bytes = + (std::max)(1u, (std::min)(uint32_t(1024 * 1024), max_read_bytes)); + +#ifdef TINYUSDZ_ANDROID_LOAD_FROM_ASSETS + if (tinyusdz::io::asset_manager) { + AAsset *asset = AAssetManager_open(asset_manager, filepath.c_str(), + AASSET_MODE_STREAMING); + if (!asset) { + if (err) { + (*err) += "File open error(from AssestManager) : " + filepath + "\n"; + } + return false; + } + size_t size = AAsset_getLength(asset); + if (size == 0) { + if (err) { + (*err) += "Invalid file size : " + filepath + + " (does the path point to a directory?)"; + } + return false; + } + + size = (std::min)(size_t(max_read_bytes), size); + out->resize(size); + AAsset_read(asset, reinterpret_cast(&out->at(0)), size); + AAsset_close(asset); + return true; + } else { + if (err) { + (*err) += "No asset manager specified : " + filepath + "\n"; + } + return false; + } + +#else +#ifdef _WIN32 +#if defined(__GLIBCXX__) // mingw + int file_descriptor = + _wopen(UTF8ToWchar(filepath).c_str(), _O_RDONLY | _O_BINARY); + __gnu_cxx::stdio_filebuf wfile_buf(file_descriptor, std::ios_base::in); + std::istream f(&wfile_buf); +#elif defined(_MSC_VER) || defined(_LIBCPP_VERSION) + // For libcxx, assume _LIBCPP_HAS_OPEN_WITH_WCHAR is defined to accept + // `wchar_t *` + std::ifstream f(UTF8ToWchar(filepath).c_str(), std::ifstream::binary); +#else + // Unknown compiler/runtime + std::ifstream f(filepath.c_str(), std::ifstream::binary); +#endif +#else + std::ifstream f(filepath.c_str(), std::ifstream::binary); +#endif + if (!f) { + if (err) { + (*err) += "File does not exit or open error : " + filepath + "\n"; + } + return false; + } + + // For directory(and pipe?), peek() will fail(Posix gnustl/libc++ only) + int buf = f.peek(); + (void)buf; + if (!f) { + if (err) { + (*err) += "File read error. Maybe empty file or invalid file : " + filepath + "\n"; + } + return false; + } + + + f.seekg(0, f.end); + size_t sz = static_cast(f.tellg()); + f.seekg(0, f.beg); + + if (int64_t(sz) < 0) { + if (err) { + (*err) += "Invalid file size : " + filepath + + " (does the path point to a directory?)"; + } + return false; + } else if (sz == 0) { + if (err) { + (*err) += "File is empty : " + filepath + "\n"; + } + return false; + } else if (uint64_t(sz) >= uint64_t(std::numeric_limits::max())) { + // Posixish environment. + if (err) { + (*err) += "Invalid File(Pipe or special device?) : " + filepath + "\n"; + } + return false; + } + + sz = (std::min)(size_t(max_read_bytes), sz); + + out->resize(sz); + f.read(reinterpret_cast(&out->at(0)), + static_cast(sz)); + + if (!f) { + // read failure. + if (err) { + (*err) += "Failed to read file: " + filepath + "\n"; + } + return false; + } + + return true; +#endif +} + +bool WriteWholeFile(const std::string &filepath, const unsigned char *contents, + size_t content_bytes, std::string *err) { +#ifdef _WIN32 +#if defined(__GLIBCXX__) // mingw + int file_descriptor = _wopen(UTF8ToWchar(filepath).c_str(), + _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY); + __gnu_cxx::stdio_filebuf wfile_buf( + file_descriptor, std::ios_base::out | std::ios_base::binary); + std::ostream f(&wfile_buf); +#elif defined(_MSC_VER) || defined(_LIBCPP_VERSION) + std::ofstream f(UTF8ToWchar(filepath).c_str(), std::ofstream::binary); +#else // other C++ compiler for win32? + std::ofstream f(filepath.c_str(), std::ofstream::binary); +#endif +#else + std::ofstream f(filepath.c_str(), std::ofstream::binary); +#endif + if (!f) { + if (err) { + (*err) += "File open error for writing : " + filepath + "\n"; + } + return false; + } + + f.write(reinterpret_cast(contents), + static_cast(content_bytes)); + if (!f) { + if (err) { + (*err) += "File write error: " + filepath + "\n"; + } + return false; + } + + return true; +} + +#ifdef _WIN32 +bool WriteWholeFile(const std::wstring &filepath, const unsigned char *contents, + size_t content_bytes, std::string *err) { +#if defined(__GLIBCXX__) // mingw + int file_descriptor = + _wopen(filepath.c_str(), _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY); + __gnu_cxx::stdio_filebuf wfile_buf( + file_descriptor, std::ios_base::out | std::ios_base::binary); + std::ostream f(&wfile_buf); +#elif defined(_MSC_VER) || defined(_LIBCPP_VERSION) + // MSVC extension allow wstrng as an argument. + std::ofstream f(filepath.c_str(), std::ofstream::binary); +#else // other C++ compiler for win32? +#error "Unsupporte platform" +#endif + + if (!f) { + if (err) { + // This would print garbage character... + // FIXME: First create string in wchar, then convert to wstring? + (*err) += "File open error for writing : " + WcharToUTF8(filepath) + "\n"; + } + return false; + } + + f.write(reinterpret_cast(contents), + static_cast(content_bytes)); + if (!f) { + if (err) { + (*err) += "File write error: " + WcharToUTF8(filepath) + "\n"; + } + return false; + } + + return true; +} +#endif + +std::string GetBaseDir(const std::string &filepath) { + if (filepath.find_last_of("/\\") != std::string::npos) + return filepath.substr(0, filepath.find_last_of("/\\")); + return ""; +} + +std::string GetFileExtension(const std::string &FileName) { + if (FileName.find_last_of(".") != std::string::npos) + return FileName.substr(FileName.find_last_of(".") + 1); + return ""; +} + +std::string GetBaseFilename(const std::string &filepath) { + auto idx = filepath.find_last_of("/\\"); + if (idx != std::string::npos) return filepath.substr(idx + 1); + return filepath; +} + +bool IsAbsPath(const std::string &filename) { + if (filename.size() > 0) { + if (filename[0] == '/') { + return true; + } + } + + // UNC path? + if (filename.size() > 2) { + if ((filename[0] == '\\') && (filename[1] == '\\')) { + return true; + } + } + + // TODO: Windows drive path(e.g. C:\, D:\, ...) + + return false; +} + +std::string JoinPath(const std::string &dir, const std::string &filename) { + if (dir.empty()) { + return filename; + } else { + // check '/' + char lastChar = *dir.rbegin(); + + // TODO: Support more relative path case. + + std::string basedir; + if (lastChar != '/') { + basedir = dir + std::string("/"); + } else { + basedir = dir; + } + + if (basedir.size()) { + if (startsWith(filename, "./")) { + // strip "./" + return basedir + removePrefix(filename, "./"); + } + return basedir + filename; + } else { + return filename; + } + } +} + +bool USDFileExists(const std::string &fpath) { + size_t read_len = 9; // USD file must be at least 9 bytes or more. + + std::string err; + std::vector data; + + if (!ReadFileHeader(&data, &err, fpath, uint32_t(read_len))) { + return false; + } + + return true; +} + +bool IsUDIMPath(const std::string &path) { + return SplitUDIMPath(path, nullptr, nullptr); +} + +bool SplitUDIMPath(const std::string &path, std::string *pre, + std::string *post) { + std::string tag = ""; + + auto rs = std::search(path.begin(), path.end(), tag.begin(), tag.end()); + if (rs == path.end()) { + return false; + } + + auto re = std::find_end(path.begin(), path.end(), tag.begin(), tag.end()); + if (re == path.end()) { + return false; + } + + // No multiple tags. e.g. diffuse...png + if (rs != re) { + return false; + } + + if (pre) { + (*pre) = std::string(path.begin(), rs); + } + + if (post) { + (*post) = std::string(re, path.end()); + } + + return true; +} + +bool FileExists(const std::string &filepath, void *userdata) { + (void)userdata; + + bool ret{false}; +#ifdef TINYUSDZ_ANDROID_LOAD_FROM_ASSETS + if (asset_manager) { + AAsset *asset = AAssetManager_open(asset_manager, filepath.c_str(), + AASSET_MODE_STREAMING); + if (!asset) { + return false; + } + AAsset_close(asset); + ret = true; + } else { + return false; + } +#else +#ifdef _WIN32 +#if defined(_MSC_VER) || defined(__GLIBCXX__) || defined(_LIBCPP_VERSION) + FILE *fp = nullptr; + errno_t err = _wfopen_s(&fp, UTF8ToWchar(filepath).c_str(), L"rb"); + if (err != 0) { + return false; + } +#else + FILE *fp = nullptr; + errno_t err = fopen_s(&fp, filepath.c_str(), "rb"); + if (err != 0) { + return false; + } +#endif + +#else + FILE *fp = fopen(filepath.c_str(), "rb"); +#endif + if (fp) { + ret = true; + fclose(fp); + } else { + ret = false; + } +#endif + + return ret; +} + +std::string FindFile(const std::string &filename, + const std::vector &search_paths) { + // TODO: Use ghc filesystem? + + if (filename.empty()) { + return filename; + } + + if (search_paths.empty()) { + std::string absPath = io::ExpandFilePath(filename, /* userdata */ nullptr); + if (io::FileExists(absPath, /* userdata */ nullptr)) { + return absPath; + } + } + + for (size_t i = 0; i < search_paths.size(); i++) { + std::string absPath = io::ExpandFilePath( + io::JoinPath(search_paths[i], filename), /* userdata */ nullptr); + if (io::FileExists(absPath, /* userdata */ nullptr)) { + return absPath; + } + } + + return std::string(); +} + +} // namespace io +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/io-util.hh b/contrib/tinyusdz/tinyusdz_repo/src/io-util.hh new file mode 100644 index 000000000..18a7e98a8 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/io-util.hh @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +#pragma once + +#include +#include +#include +#include +#include +#include + +#ifdef TINYUSDZ_ANDROID_LOAD_FROM_ASSETS +#include +#endif + +namespace tinyusdz { +namespace io { + +// TODO: Move texture-utils.hh or somewhere, not here. +// +// : 1001 ~ 1100 +// : u1_v1 ~ u10_v10 +// +struct UDIMAsset +{ + // up to 10x10 tiles + uint32_t index{1001}; // [1001, 1100] + + std::string asset_identifier; // usually filename or URI +}; + +struct UDIMAssetTiles +{ + std::map tiles; + + // tile u, v : 0-based + + static uint32_t UDIMIndex(uint32_t u, uint32_t v) { + uint32_t uu = (std::min)(9u, u); + uint32_t vv = (std::max)(9u, v); + + return 1001 + uu + vv * 10; + } + + static std::string UVTILEIndex(uint32_t u, uint32_t v) { + uint32_t uu = (std::min)(9u, u); + uint32_t vv = (std::max)(9u, v); + + return "u" + std::to_string(uu+1) + "_" + std::to_string(vv+1); + } + + bool IsValidTile(uint32_t u, uint32_t v) { + if (u > 9) return false; + if (v > 9) return false; + + return true; + } + + bool has_tile(uint32_t u, uint32_t v) { + if (u > 9) return false; + if (v > 9) return false; + + uint32_t tid = UDIMIndex(u, v); + return tiles.count(tid); + } + + bool set(uint32_t u, uint32_t v, const UDIMAsset &asset) { + if (!IsValidTile(u, v)) { + return false; + } + + tiles.emplace(UDIMIndex(u, v), asset); + + return true; + } + + bool erase(uint32_t u, uint32_t v) { + if (!IsValidTile(u, v)) { + return false; + } + + tiles.erase(UDIMIndex(u, v)); + + return true; + } + +}; + +#ifdef TINYUSDZ_ANDROID_LOAD_FROM_ASSETS +extern AAssetManager *asset_manager; +#endif + +#ifdef _WIN32 +std::wstring UTF8ToWchar(const std::string &str); +std::string WcharToUTF8(const std::wstring &wstr); +#endif + +std::string ExpandFilePath(const std::string &filepath, + void *userdata = nullptr); + +bool FileExists(const std::string &filepath, void *userdata = nullptr); + +/// +/// Find file from search paths. +/// Returns empty string if a file is not found. +/// TODO: Filesystem callback. +/// +std::string FindFile(const std::string &filepath, const std::vector &search_paths); + +bool ReadWholeFile(std::vector *out, std::string *err, + const std::string &filepath, size_t filesize_max = 0, + void *userdata = nullptr); + +/// +/// Read first N bytes from a file. +/// Example is for detect file formats. +/// +bool ReadFileHeader(std::vector *out, std::string *err, + const std::string &filepath, uint32_t max_read_bytes = 128, + void *userdata = nullptr); + + +/// +/// @return true when the system supports mmap. +/// +bool IsMMapSupported(); + +// Simple mmap file handle struct +struct MMapFileHandle +{ + std::string filename; + bool writable{false}; + uint8_t *addr{nullptr}; + size_t size{0}; +}; + +/// +/// memory-map file. +/// Returns false when file is not found, invalid, or mmap feature is not available. +/// +bool MMapFile(const std::string &filepath, MMapFileHandle *handle, bool writable = false); + +bool UnmapFile(const MMapFileHandle &handle); + +/// +/// Filepath is treated as WideChar(UNICODE) on Windows. +/// +bool WriteWholeFile(const std::string &filepath, + const unsigned char *contents, size_t content_bytes, std::string *err); + +#ifdef _WIN32 +bool WriteWholeFile(const std::wstring &filepath, + const unsigned char *contents, size_t content_bytes, std::string *err); +#endif + +std::string GetBaseDir(const std::string &filepath); +std::string GetBaseFilename(const std::string &filepath); +std::string GetFileExtension(const std::string &filepath); + +std::string JoinPath(const std::string &dir, const std::string &filename); +bool IsAbsPath(const std::string &filepath); + +bool IsUDIMPath(const std::string &filepath); + + +bool USDFileExists(const std::string &filepath); + +// +// diffuse..png => "diffuse.", ".png" +// +bool SplitUDIMPath(const std::string &filepath, std::string *pre, + std::string *post); + +} // namespace io +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/linear-algebra.cc b/contrib/tinyusdz/tinyusdz_repo/src/linear-algebra.cc new file mode 100644 index 000000000..67f661e75 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/linear-algebra.cc @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022-Present Light Transport Entertainment, Inc. +#include "linear-algebra.hh" +#include "value-eval-util.hh" + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#include "external/linalg.h" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace tinyusdz { + +value::quath slerp(const value::quath &_a, const value::quath &_b, const float t) { + value::quatf a; + value::quatf b; + + a[0] = value::half_to_float(_a[0]); + a[1] = value::half_to_float(_a[1]); + a[2] = value::half_to_float(_a[2]); + a[3] = value::half_to_float(_a[3]); + + b[0] = value::half_to_float(_b[0]); + b[1] = value::half_to_float(_b[1]); + b[2] = value::half_to_float(_b[2]); + b[3] = value::half_to_float(_b[3]); + + value::quatf _c = slerp(a, b, t); + + value::quath c; + c[0] = value::float_to_half_full(_c[0]); + c[1] = value::float_to_half_full(_c[1]); + c[2] = value::float_to_half_full(_c[2]); + c[3] = value::float_to_half_full(_c[3]); + + return c; +} + +value::quatf slerp(const value::quatf &a, const value::quatf &b, const float t) { + linalg::vec qa; + linalg::vec qb; + linalg::vec qret; + + memcpy(&qa, &a, sizeof(float) * 4); + memcpy(&qb, &b, sizeof(float) * 4); + + qret = linalg::slerp(qa, qb, t); + + + return *(reinterpret_cast(&qret)); +} + +value::quatd slerp(const value::quatd &a, const value::quatd &b, const double t) { + + linalg::vec qa; + linalg::vec qb; + linalg::vec qret; + + memcpy(&qa, &a, sizeof(double) * 4); + memcpy(&qb, &b, sizeof(double) * 4); + + qret = linalg::slerp(qa, qb, t); + + return *(reinterpret_cast(&qret)); + +} + +float vlength(const value::float3 &a) { + float d2 = a[0] * a[0] + a[1] * a[1] + a[2] * a[2]; + if (d2 > std::numeric_limits::epsilon()) { + return std::sqrt(d2); + } + return 0.0f; +} + +float vlength(const value::normal3f &a) { + return vlength(value::float3{a.x, a.y, a.z}); +} + +float vlength(const value::vector3f &a) { + return vlength(value::float3{a.x, a.y, a.z}); +} + +float vlength(const value::point3f &a) { + return vlength(value::float3{a.x, a.y, a.z}); +} + +value::float3 vnormalize(const value::float3 &a, const float eps) { + float len = vlength(a); + len = (len > eps) ? len : eps; + return value::float3({a[0] / len, a[1] / len, a[2] / len}); +} + +value::normal3f vnormalize(const value::normal3f &a, const float eps) { + float len = vlength(a); + len = (len > eps) ? len : eps; + return value::normal3f({a[0] / len, a[1] / len, a[2] / len}); +} + +value::vector3f vnormalize(const value::vector3f &a, const float eps) { + float len = vlength(a); + len = (len > eps) ? len : eps; + return value::vector3f({a[0] / len, a[1] / len, a[2] / len}); +} + +value::point3f vnormalize(const value::point3f &a, const float eps) { + float len = vlength(a); + len = (len > eps) ? len : eps; + return value::point3f({a[0] / len, a[1] / len, a[2] / len}); +} + +double vlength(const value::double3 &a) { + double d2 = a[0] * a[0] + a[1] * a[1] + a[2] * a[2]; + if (d2 > std::numeric_limits::epsilon()) { + return std::sqrt(d2); + } + return 0.0; +} + +double vlength(const value::normal3d &a) { + return vlength(value::double3{a.x, a.y, a.z}); +} + +double vlength(const value::vector3d &a) { + return vlength(value::double3{a.x, a.y, a.z}); +} + +double vlength(const value::point3d &a) { + return vlength(value::double3{a.x, a.y, a.z}); +} + +value::double3 vnormalize(const value::double3 &a, const double eps) { + double len = vlength(a); + len = (len > eps) ? len : eps; + return value::double3({a[0] / len, a[1] / len, a[2] / len}); +} + +value::normal3d vnormalize(const value::normal3d &a, const double eps) { + double len = vlength(a); + len = (len > eps) ? len : eps; + return value::normal3d({a[0] / len, a[1] / len, a[2] / len}); +} + +value::vector3d vnormalize(const value::vector3d &a, const double eps) { + double len = vlength(a); + len = (len > eps) ? len : eps; + return value::vector3d({a[0] / len, a[1] / len, a[2] / len}); +} + +value::point3d vnormalize(const value::point3d &a, const double eps) { + double len = vlength(a); + len = (len > eps) ? len : eps; + return value::point3d({a[0] / len, a[1] / len, a[2] / len}); +} + +value::float3 vcross(const value::float3 &a, const value::float3 &b) +{ + value::float3 n; + n[0] = a[1] * b[2] - a[2] * b[1]; + n[1] = a[2] * b[0] - a[0] * b[2]; + n[2] = a[0] * b[1] - a[1] * b[0]; + + return n; +} + +value::double3 vcross(const value::double3 &a, const value::double3 &b) +{ + value::double3 n; + n[0] = a[1] * b[2] - a[2] * b[1]; + n[1] = a[2] * b[0] - a[0] * b[2]; + n[2] = a[0] * b[1] - a[1] * b[0]; + + return n; +} + +value::normal3f vcross(const value::normal3f &a, const value::normal3f &b) +{ + value::normal3f n; + n[0] = a[1] * b[2] - a[2] * b[1]; + n[1] = a[2] * b[0] - a[0] * b[2]; + n[2] = a[0] * b[1] - a[1] * b[0]; + + return n; +} + +value::normal3d vcross(const value::normal3d &a, const value::normal3d &b) +{ + value::normal3d n; + n[0] = a[1] * b[2] - a[2] * b[1]; + n[1] = a[2] * b[0] - a[0] * b[2]; + n[2] = a[0] * b[1] - a[1] * b[0]; + + return n; +} + +value::vector3f vcross(const value::vector3f &a, const value::vector3f &b) +{ + value::vector3f n; + n[0] = a[1] * b[2] - a[2] * b[1]; + n[1] = a[2] * b[0] - a[0] * b[2]; + n[2] = a[0] * b[1] - a[1] * b[0]; + + return n; +} + +value::vector3d vcross(const value::vector3d &a, const value::vector3d &b) +{ + value::vector3d n; + n[0] = a[1] * b[2] - a[2] * b[1]; + n[1] = a[2] * b[0] - a[0] * b[2]; + n[2] = a[0] * b[1] - a[1] * b[0]; + + return n; +} + +value::point3f vcross(const value::point3f &a, const value::point3f &b) +{ + value::point3f n; + n[0] = a[1] * b[2] - a[2] * b[1]; + n[1] = a[2] * b[0] - a[0] * b[2]; + n[2] = a[0] * b[1] - a[1] * b[0]; + + return n; +} + +value::point3d vcross(const value::point3d &a, const value::point3d &b) +{ + value::point3d n; + n[0] = a[1] * b[2] - a[2] * b[1]; + n[1] = a[2] * b[0] - a[0] * b[2]; + n[2] = a[0] * b[1] - a[1] * b[0]; + + return n; +} + +// CCW + +value::float3 geometric_normal(const value::float3 &p0, const value::float3 &p1, const value::float3 &p2) +{ + value::float3 n = vcross(p1 - p0, p2 - p0); + + return vnormalize(n); +} + +value::double3 geometric_normal(const value::double3 &p0, const value::double3 &p1, const value::double3 &p2) +{ + value::double3 n = vcross(p1 - p0, p2 - p0); + + return vnormalize(n); +} + +value::point3f geometric_normal(const value::point3f &p0, const value::point3f &p1, const value::point3f &p2) +{ + value::point3f n = vcross(p1 - p0, p2 - p0); + + return vnormalize(n); +} + +value::point3d geometric_normal(const value::point3d &p0, const value::point3d &p1, const value::point3d &p2) +{ + value::point3d n = vcross(p1 - p0, p2 - p0); + + return vnormalize(n); +} + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/linear-algebra.hh b/contrib/tinyusdz/tinyusdz_repo/src/linear-algebra.hh new file mode 100644 index 000000000..726a7f8bf --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/linear-algebra.hh @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022-Present Light Transport Entertainment, Inc. +#pragma once + +#include "value-types.hh" + +namespace tinyusdz { + +constexpr float kFloatNormalizeEps = std::numeric_limits::epsilon(); +constexpr double kDoubleNormalizeEps = std::numeric_limits::epsilon(); + +// GF_MIN_VECTOR_LENGTH in pxrUSD +constexpr double kPXRNormalizeEps = 1.0e-10; + +value::quath slerp(const value::quath &a, const value::quath &b, const float t); +value::quatf slerp(const value::quatf &a, const value::quatf &b, const float t); +value::quatd slerp(const value::quatd &a, const value::quatd &b, const double t); + +float vlength(const value::float3 &a); +float vlength(const value::normal3f &a); +float vlength(const value::vector3f &a); +float vlength(const value::point3f &a); +double vlength(const value::double3 &a); +double vlength(const value::normal3d &a); +double vlength(const value::vector3d &a); +double vlength(const value::point3d &a); + +value::float3 vnormalize(const value::float3 &a, const float eps = kFloatNormalizeEps); +value::double3 vnormalize(const value::double3 &a, const double eps = kDoubleNormalizeEps); +value::normal3f vnormalize(const value::normal3f &a, const float eps = kFloatNormalizeEps); +value::normal3d vnormalize(const value::normal3d &a, const double eps = kDoubleNormalizeEps); +value::vector3f vnormalize(const value::vector3f &a, const float eps = kFloatNormalizeEps); +value::vector3d vnormalize(const value::vector3d &a, const double eps = kDoubleNormalizeEps); +value::point3f vnormalize(const value::point3f &a, const float eps = kFloatNormalizeEps); +value::point3d vnormalize(const value::point3d &a, const double eps = kDoubleNormalizeEps); + +// Assume CCW(Counter ClockWise) +value::float3 vcross(const value::float3 &a, const value::float3 &b); +value::double3 vcross(const value::double3 &a, const value::double3 &b); +value::normal3f vcross(const value::normal3f &a, const value::normal3f &b); +value::normal3d vcross(const value::normal3d &a, const value::normal3d &b); +value::vector3f vcross(const value::vector3f &a, const value::vector3f &b); +value::vector3d vcross(const value::vector3d &a, const value::vector3d &b); +value::point3f vcross(const value::point3f &a, const value::point3f &b); +value::point3d vcross(const value::point3d &a, const value::point3d &b); + +value::float3 geometric_normal(const value::float3 &p0, const value::float3 &p1, const value::float3 &p2); +value::double3 geometric_normal(const value::double3 &p0, const value::double3 &p1, const value::double3 &p2); +value::point3f geometric_normal(const value::point3f &p0, const value::point3f &p1, const value::point3f &p2); +value::point3d geometric_normal(const value::point3d &p0, const value::point3d &p1, const value::point3d &p2); + + + +inline float vdot(const value::float3 &a, const value::float3 &b) +{ + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +inline double vdot(const value::double3 &a, const value::double3 &b) +{ + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +inline float vdot(const value::vector3f &a, const value::vector3f &b) +{ + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +inline double vdot(const value::vector3d &a, const value::vector3d &b) +{ + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +inline float vdot(const value::normal3f &a, const value::normal3f &b) +{ + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +inline double vdot(const value::normal3d &a, const value::normal3d &b) +{ + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/lz4-compression.cc b/contrib/tinyusdz/tinyusdz_repo/src/lz4-compression.cc new file mode 100644 index 000000000..b096227f0 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/lz4-compression.cc @@ -0,0 +1,259 @@ +#ifdef _MSC_VER +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + +// +#include +#include +#include +#include +#include + +// + +/* Suppress DEPRECATE macro warnings */ +#define LZ4_DISABLE_DEPRECATE_WARNINGS + +#include "lz4-compression.hh" +#include "common-macros.inc" + + + + +// LZ4Compression based on USD's TfFastCompression class + +// +// Copyright 2017 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "Apache License") +// with the following modification; you may not use this file except in +// compliance with the Apache License and the following modification to it: +// Section 6. Trademarks. is deleted and replaced with: +// +// 6. Trademarks. This License does not grant permission to use the trade +// names, trademarks, service marks, or product names of the Licensor +// and its affiliates, except as required to comply with Section 4(c) of +// the License and to reproduce the content of the NOTICE file. +// +// You may obtain a copy of the Apache License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the Apache License with the above modification is +// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the Apache License for the specific +// language governing permissions and limitations under the Apache License. +// + +#if 0 +//#include "pxrLZ4/lz4.h" +//using namespace pxr_lz4; +#else +// TinyUSDZ : Use orignal lz4. +#include "lz4/lz4.h" +#endif + +namespace tinyusdz { + +size_t LZ4Compression::GetMaxInputSize() { + return 127 * static_cast(LZ4_MAX_INPUT_SIZE); +} + +size_t LZ4Compression::GetCompressedBufferSize(size_t inputSize) { + if (inputSize > GetMaxInputSize()) return 0; + + // If it fits in one chunk then it's just the compress bound plus 1. + if (inputSize <= LZ4_MAX_INPUT_SIZE) { + return size_t(LZ4_compressBound(int(inputSize))) + 1; + } + size_t nWholeChunks = inputSize / LZ4_MAX_INPUT_SIZE; + size_t partChunkSz = inputSize % LZ4_MAX_INPUT_SIZE; + size_t sz = 1 + nWholeChunks * + (size_t(LZ4_compressBound(LZ4_MAX_INPUT_SIZE)) + sizeof(int32_t)); + if (partChunkSz) sz += size_t(LZ4_compressBound(int(partChunkSz))) + sizeof(int32_t); + return sz; +} + +size_t LZ4Compression::CompressToBuffer(char const *input, char *compressed, + size_t inputSize, std::string *err) { + if (inputSize > GetMaxInputSize()) { + if (err) { + (*err) = "Attempted to compress a buffer of " + + std::to_string(inputSize) + + " bytes, " + "more than the maximum supported " + + std::to_string(GetMaxInputSize()) + "\n"; + } + return 0; + } + + // If it fits in one chunk, just do it. + char const *const origCompressed = compressed; + if (inputSize <= LZ4_MAX_INPUT_SIZE) { + compressed[0] = 0; // < zero byte means one chunk. + compressed += 1 + LZ4_compress_default(input, compressed + 1, int(inputSize), + int(GetCompressedBufferSize(inputSize))); + } else { + size_t nWholeChunks = inputSize / LZ4_MAX_INPUT_SIZE; + size_t partChunkSz = inputSize % LZ4_MAX_INPUT_SIZE; + size_t numChunks = nWholeChunks + (partChunkSz ? 1 : 0); + + if (numChunks > 127) { + if (err) { + (*err) = "# of chunks must be less than 127 but got " + std::to_string(numChunks) + "\n"; + } + return 0; + } + *compressed++ = char(numChunks); + auto writeChunk = [](char const *&_input, char *&_output, size_t size) { + char *o = _output; + _output += sizeof(int32_t); + int32_t n = + LZ4_compress_default(_input, _output, int(size), LZ4_compressBound(int(size))); + memcpy(o, &n, sizeof(n)); + _output += n; + _input += size; + }; + for (size_t chunk = 0; chunk != nWholeChunks; ++chunk) { + writeChunk(input, compressed, LZ4_MAX_INPUT_SIZE); + } + if (partChunkSz) { + writeChunk(input, compressed, partChunkSz); + } + } + + return size_t(compressed - origCompressed); +} + +size_t LZ4Compression::DecompressFromBuffer(char const *compressedPtr, + char *outputPtr, + size_t compressedSize, + size_t maxOutputSize, + std::string *err) { + if (compressedSize <= 1) { + if (err) { + (*err) = + "Invalid compressedSize.\n"; + } + return 0; + } + + // Check first byte for # chunks. + int nChunks = *compressedPtr++; + if (nChunks > 127) { + if (err) { + (*err) = + "Too many chunks in LZ4 compressed data.\n"; + } + return 0; + } + + DCOUT("compressedSize = " << compressedSize); + DCOUT("maxOutputSize = " << maxOutputSize); + DCOUT("nChunks = " << nChunks); + //std::cout << "compressedSize = " << compressedSize << "\n"; + //std::cout << "maxOutputSize = " << maxOutputSize << "\n"; + //std::cout << "nChunks = " << nChunks << "\n"; + + size_t consumedCompressedSize = 1; + + if (maxOutputSize < LZ4_MAX_INPUT_SIZE) { + // nChunks must be 0 for < LZ4_MAX_INPUT_SIZE + if (nChunks != 0) { + if (err) { + (*err) = "Corrupted LZ4 compressed data.\n"; + } + return 0; + } + } + + if (nChunks == 0) { + // Just one. + int nDecompressed = LZ4_decompress_safe(compressedPtr, outputPtr, + int(compressedSize - 1), int(maxOutputSize)); + if (nDecompressed < 0) { + if (err) { + (*err) = + "Failed to decompress data, possibly corrupt? " + "LZ4 error code: " + + std::to_string(nDecompressed) + "\n"; + } + return 0; + } + return size_t(nDecompressed); + } else { + // Do each chunk. + size_t totalDecompressed = 0; + for (int i = 0; i < nChunks; ++i) { + int32_t chunkSize = 0; + if (consumedCompressedSize + sizeof(chunkSize) > compressedSize) { + if (err) { + (*err) += "Corrupted chunk data."; + } + return 0; + + } + + memcpy(&chunkSize, compressedPtr, sizeof(chunkSize)); + + if (chunkSize > LZ4_MAX_INPUT_SIZE) { + if (err) { + (*err) += "ChunkSize exceeds LZ4_MAX_INPUT_SIZE.\n"; + } + return 0; + } + if (chunkSize <= 0) { + if (err) { + (*err) += "Invalid ChunkSize.\n"; + } + return 0; + } + + DCOUT("chunk[" << i << "] size = " << chunkSize); + + //std::cout << "chunkSize = " << chunkSize << "\n"; + consumedCompressedSize += sizeof(chunkSize); + //std::cout << "consumedCompressedSize = " << consumedCompressedSize << "\n"; + //std::cout << "compressedSize = " << compressedSize << "\n"; + if (consumedCompressedSize > compressedSize) { + if (err) { + (*err) += "Total chunk size exceeds input compressedSize.\n"; + } + return 0; + } + + compressedPtr += sizeof(chunkSize); + int nDecompressed = LZ4_decompress_safe( + compressedPtr, outputPtr, chunkSize, + int(std::min(LZ4_MAX_INPUT_SIZE, maxOutputSize))); + if (nDecompressed <= 0) { + if (err) { + (*err) = + "Failed to decompress data, possibly corrupt? " + "LZ4 error code: " + + std::to_string(nDecompressed) + "\n"; + } + return 0; + } + //std::cout << "nDecompressed = " << nDecompressed << "\n"; + if (nDecompressed > maxOutputSize) { + if (err) { + (*err) = + "Failed to decompress data, possibly corrupt?\n"; + } + return 0; + } + compressedPtr += chunkSize; + outputPtr += nDecompressed; + maxOutputSize -= size_t(nDecompressed); + totalDecompressed += size_t(nDecompressed); + } + return totalDecompressed; + } + // unreachable. +} + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/lz4-compression.hh b/contrib/tinyusdz/tinyusdz_repo/src/lz4-compression.hh new file mode 100644 index 000000000..08d6a48df --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/lz4-compression.hh @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +namespace tinyusdz { + +// Based on USD' TfFastCompression + +class LZ4Compression +{ +public: + /// Return the largest input buffer size that can be compressed with these + /// functions. Guaranteed to be at least 200 GB. + static size_t + GetMaxInputSize(); + + /// Return the largest possible compressed size for the given \p inputSize + /// in the worst case (input is not compressible). This is larger than + /// \p inputSize. If inputSize is larger than GetMaxInputSize(), return 0. + static size_t + GetCompressedBufferSize(size_t inputSize); + + /// Compress \p inputSize bytes in \p input and store the result in + /// \p compressed. The \p compressed buffer must point to at least + /// GetCompressedBufferSize(uncompressedSize) bytes. Return the number of + /// bytes written to the \p compressed buffer. Issue a runtime error and + /// return ~0 in case of an error. + static size_t + CompressToBuffer(char const *input, char *compressed, size_t inputSize, std::string *err); + + /// Decompress \p compressedSize bytes in \p compressed and store the + /// result in \p output. No more than \p maxOutputSize bytes will be + /// written to \p output. + static size_t + DecompressFromBuffer(char const *compressed, char *output, + size_t compressedSize, size_t maxOutputSize, std::string *err); +}; + + + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/lz4/LICENSE b/contrib/tinyusdz/tinyusdz_repo/src/lz4/LICENSE new file mode 100644 index 000000000..488491695 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/lz4/LICENSE @@ -0,0 +1,24 @@ +LZ4 Library +Copyright (c) 2011-2020, Yann Collet +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/contrib/tinyusdz/tinyusdz_repo/src/lz4/lz4.c b/contrib/tinyusdz/tinyusdz_repo/src/lz4/lz4.c new file mode 100644 index 000000000..d0950cf1f --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/lz4/lz4.c @@ -0,0 +1,2734 @@ +/* + LZ4 - Fast LZ compression algorithm + Copyright (C) 2011-2020, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/lz4/lz4 +*/ + +/*-************************************ +* Tuning parameters +**************************************/ + + +/* + * LZ4_HEAPMODE : + * Select how default compression functions will allocate memory for their hash table, + * in memory stack (0:default, fastest), or in memory heap (1:requires malloc()). + */ +#ifndef LZ4_HEAPMODE +/* TinyUSDZ modification for security */ +# define LZ4_HEAPMODE 1 +#endif + +/* + * LZ4_ACCELERATION_DEFAULT : + * Select "acceleration" for LZ4_compress_fast() when parameter value <= 0 + */ +#define LZ4_ACCELERATION_DEFAULT 1 +/* + * LZ4_ACCELERATION_MAX : + * Any "acceleration" value higher than this threshold + * get treated as LZ4_ACCELERATION_MAX instead (fix #876) + */ +#define LZ4_ACCELERATION_MAX 65537 + + +/*-************************************ +* CPU Feature Detection +**************************************/ +/* LZ4_FORCE_MEMORY_ACCESS + * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. + * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. + * The below switch allow to select different access method for improved performance. + * Method 0 (default) : use `memcpy()`. Safe and portable. + * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable). + * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. + * Method 2 : direct access. This method is portable but violate C standard. + * It can generate buggy code on targets which assembly generation depends on alignment. + * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6) + * See https://fastcompression.blogspot.fr/2015/08/accessing-unaligned-memory.html for details. + * Prefer these methods in priority order (0 > 1 > 2) + */ +#ifndef LZ4_FORCE_MEMORY_ACCESS /* can be defined externally */ +# if defined(__GNUC__) && \ + ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) \ + || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) +/* TinyUSDZ modification for security */ +/*# define LZ4_FORCE_MEMORY_ACCESS 2 */ +# define LZ4_FORCE_MEMORY_ACCESS 0 +# elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || defined(__GNUC__) +/* TinyUSDZ modification for security */ +/*# define LZ4_FORCE_MEMORY_ACCESS 1 */ +# define LZ4_FORCE_MEMORY_ACCESS 0 +# endif +#endif + +/* + * LZ4_FORCE_SW_BITCOUNT + * Define this parameter if your target system or compiler does not support hardware bit count + */ +#if defined(_MSC_VER) && defined(_WIN32_WCE) /* Visual Studio for WinCE doesn't support Hardware bit count */ +# undef LZ4_FORCE_SW_BITCOUNT /* avoid double def */ +# define LZ4_FORCE_SW_BITCOUNT +#endif + + + +/*-************************************ +* Dependency +**************************************/ +/* + * LZ4_SRC_INCLUDED: + * Amalgamation flag, whether lz4.c is included + */ +#ifndef LZ4_SRC_INCLUDED +# define LZ4_SRC_INCLUDED 1 +#endif + +#ifndef LZ4_STATIC_LINKING_ONLY +#define LZ4_STATIC_LINKING_ONLY +#endif + +#ifndef LZ4_DISABLE_DEPRECATE_WARNINGS +#define LZ4_DISABLE_DEPRECATE_WARNINGS /* due to LZ4_decompress_safe_withPrefix64k */ +#endif + +#define LZ4_STATIC_LINKING_ONLY /* LZ4_DISTANCE_MAX */ +#include "lz4.h" +/* see also "memory routines" below */ + + +/*-************************************ +* Compiler Options +**************************************/ +#if defined(_MSC_VER) && (_MSC_VER >= 1400) /* Visual Studio 2005+ */ +# include /* only present in VS2005+ */ +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +# pragma warning(disable : 6237) /* disable: C6237: conditional expression is always 0 */ +#endif /* _MSC_VER */ + +#ifndef LZ4_FORCE_INLINE +# ifdef _MSC_VER /* Visual Studio */ +# define LZ4_FORCE_INLINE static __forceinline +# else +# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ +# ifdef __GNUC__ +# define LZ4_FORCE_INLINE static inline __attribute__((always_inline)) +# else +# define LZ4_FORCE_INLINE static inline +# endif +# else +# define LZ4_FORCE_INLINE static +# endif /* __STDC_VERSION__ */ +# endif /* _MSC_VER */ +#endif /* LZ4_FORCE_INLINE */ + +/* LZ4_FORCE_O2 and LZ4_FORCE_INLINE + * gcc on ppc64le generates an unrolled SIMDized loop for LZ4_wildCopy8, + * together with a simple 8-byte copy loop as a fall-back path. + * However, this optimization hurts the decompression speed by >30%, + * because the execution does not go to the optimized loop + * for typical compressible data, and all of the preamble checks + * before going to the fall-back path become useless overhead. + * This optimization happens only with the -O3 flag, and -O2 generates + * a simple 8-byte copy loop. + * With gcc on ppc64le, all of the LZ4_decompress_* and LZ4_wildCopy8 + * functions are annotated with __attribute__((optimize("O2"))), + * and also LZ4_wildCopy8 is forcibly inlined, so that the O2 attribute + * of LZ4_wildCopy8 does not affect the compression speed. + */ +#if defined(__PPC64__) && defined(__LITTLE_ENDIAN__) && defined(__GNUC__) && !defined(__clang__) +# define LZ4_FORCE_O2 __attribute__((optimize("O2"))) +# undef LZ4_FORCE_INLINE +# define LZ4_FORCE_INLINE static __inline __attribute__((optimize("O2"),always_inline)) +#else +# define LZ4_FORCE_O2 +#endif + +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__) +# define expect(expr,value) (__builtin_expect ((expr),(value)) ) +#else +# define expect(expr,value) (expr) +#endif + +#ifndef likely +#define likely(expr) expect((expr) != 0, 1) +#endif +#ifndef unlikely +#define unlikely(expr) expect((expr) != 0, 0) +#endif + +/* Should the alignment test prove unreliable, for some reason, + * it can be disabled by setting LZ4_ALIGN_TEST to 0 */ +#ifndef LZ4_ALIGN_TEST /* can be externally provided */ +# define LZ4_ALIGN_TEST 1 +#endif + + +/*-************************************ +* Memory routines +**************************************/ + +/*! LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION : + * Disable relatively high-level LZ4/HC functions that use dynamic memory + * allocation functions (malloc(), calloc(), free()). + * + * Note that this is a compile-time switch. And since it disables + * public/stable LZ4 v1 API functions, we don't recommend using this + * symbol to generate a library for distribution. + * + * The following public functions are removed when this symbol is defined. + * - lz4 : LZ4_createStream, LZ4_freeStream, + * LZ4_createStreamDecode, LZ4_freeStreamDecode, LZ4_create (deprecated) + * - lz4hc : LZ4_createStreamHC, LZ4_freeStreamHC, + * LZ4_createHC (deprecated), LZ4_freeHC (deprecated) + * - lz4frame, lz4file : All LZ4F_* functions + */ +#if defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +# define ALLOC(s) lz4_error_memory_allocation_is_disabled +# define ALLOC_AND_ZERO(s) lz4_error_memory_allocation_is_disabled +# define FREEMEM(p) lz4_error_memory_allocation_is_disabled +#elif defined(LZ4_USER_MEMORY_FUNCTIONS) +/* memory management functions can be customized by user project. + * Below functions must exist somewhere in the Project + * and be available at link time */ +void* LZ4_malloc(size_t s); +void* LZ4_calloc(size_t n, size_t s); +void LZ4_free(void* p); +# define ALLOC(s) LZ4_malloc(s) +# define ALLOC_AND_ZERO(s) LZ4_calloc(1,s) +# define FREEMEM(p) LZ4_free(p) +#else +# include /* malloc, calloc, free */ +# define ALLOC(s) malloc(s) +# define ALLOC_AND_ZERO(s) calloc(1,s) +# define FREEMEM(p) free(p) +#endif + +#if ! LZ4_FREESTANDING +# include /* memset, memcpy */ +#endif +#if !defined(LZ4_memset) +# define LZ4_memset(p,v,s) memset((p),(v),(s)) +#endif +#define MEM_INIT(p,v,s) LZ4_memset((p),(v),(s)) + + +/*-************************************ +* Common Constants +**************************************/ +#define MINMATCH 4 + +#define WILDCOPYLENGTH 8 +#define LASTLITERALS 5 /* see ../doc/lz4_Block_format.md#parsing-restrictions */ +#define MFLIMIT 12 /* see ../doc/lz4_Block_format.md#parsing-restrictions */ +#define MATCH_SAFEGUARD_DISTANCE ((2*WILDCOPYLENGTH) - MINMATCH) /* ensure it's possible to write 2 x wildcopyLength without overflowing output buffer */ +#define FASTLOOP_SAFE_DISTANCE 64 +static const int LZ4_minLength = (MFLIMIT+1); + +#define KB *(1 <<10) +#define MB *(1 <<20) +#define GB *(1U<<30) + +#define LZ4_DISTANCE_ABSOLUTE_MAX 65535 +#if (LZ4_DISTANCE_MAX > LZ4_DISTANCE_ABSOLUTE_MAX) /* max supported by LZ4 format */ +# error "LZ4_DISTANCE_MAX is too big : must be <= 65535" +#endif + +#define ML_BITS 4 +#define ML_MASK ((1U<=1) +# include +#else +# ifndef assert +# define assert(condition) ((void)0) +# endif +#endif + +#define LZ4_STATIC_ASSERT(c) { enum { LZ4_static_assert = 1/(int)(!!(c)) }; } /* use after variable declarations */ + +#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=2) +# include + static int g_debuglog_enable = 1; +# define DEBUGLOG(l, ...) { \ + if ((g_debuglog_enable) && (l<=LZ4_DEBUG)) { \ + fprintf(stderr, __FILE__ ": "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " \n"); \ + } } +#else +# define DEBUGLOG(l, ...) {} /* disabled */ +#endif + +static int LZ4_isAligned(const void* ptr, size_t alignment) +{ + return ((size_t)ptr & (alignment -1)) == 0; +} + + +/*-************************************ +* Types +**************************************/ +#include +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# include + typedef uint8_t BYTE; + typedef uint16_t U16; + typedef uint32_t U32; + typedef int32_t S32; + typedef uint64_t U64; + typedef uintptr_t uptrval; +#else +# if UINT_MAX != 4294967295UL +# error "LZ4 code (when not C++ or C99) assumes that sizeof(int) == 4" +# endif + typedef unsigned char BYTE; + typedef unsigned short U16; + typedef unsigned int U32; + typedef signed int S32; + typedef unsigned long long U64; + typedef size_t uptrval; /* generally true, except OpenVMS-64 */ +#endif + +#if defined(__x86_64__) + typedef U64 reg_t; /* 64-bits in x32 mode */ +#else + typedef size_t reg_t; /* 32-bits in x32 mode */ +#endif + +typedef enum { + notLimited = 0, + limitedOutput = 1, + fillOutput = 2 +} limitedOutput_directive; + + +/*-************************************ +* Reading and writing into memory +**************************************/ + +/** + * LZ4 relies on memcpy with a constant size being inlined. In freestanding + * environments, the compiler can't assume the implementation of memcpy() is + * standard compliant, so it can't apply its specialized memcpy() inlining + * logic. When possible, use __builtin_memcpy() to tell the compiler to analyze + * memcpy() as if it were standard compliant, so it can inline it in freestanding + * environments. This is needed when decompressing the Linux Kernel, for example. + */ +#if !defined(LZ4_memcpy) +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4_memcpy(dst, src, size) __builtin_memcpy(dst, src, size) +# else +# define LZ4_memcpy(dst, src, size) memcpy(dst, src, size) +# endif +#endif + +#if !defined(LZ4_memmove) +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4_memmove __builtin_memmove +# else +# define LZ4_memmove memmove +# endif +#endif + +static unsigned LZ4_isLittleEndian(void) +{ + const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ + return one.c[0]; +} + + +#if defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==2) +/* lie to the compiler about data alignment; use with caution */ + +static U16 LZ4_read16(const void* memPtr) { return *(const U16*) memPtr; } +static U32 LZ4_read32(const void* memPtr) { return *(const U32*) memPtr; } +static reg_t LZ4_read_ARCH(const void* memPtr) { return *(const reg_t*) memPtr; } + +static void LZ4_write16(void* memPtr, U16 value) { *(U16*)memPtr = value; } +static void LZ4_write32(void* memPtr, U32 value) { *(U32*)memPtr = value; } + +#elif defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==1) + +/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ +/* currently only defined for gcc and icc */ +typedef union { U16 u16; U32 u32; reg_t uArch; } __attribute__((packed)) LZ4_unalign; + +static U16 LZ4_read16(const void* ptr) { return ((const LZ4_unalign*)ptr)->u16; } +static U32 LZ4_read32(const void* ptr) { return ((const LZ4_unalign*)ptr)->u32; } +static reg_t LZ4_read_ARCH(const void* ptr) { return ((const LZ4_unalign*)ptr)->uArch; } + +static void LZ4_write16(void* memPtr, U16 value) { ((LZ4_unalign*)memPtr)->u16 = value; } +static void LZ4_write32(void* memPtr, U32 value) { ((LZ4_unalign*)memPtr)->u32 = value; } + +#else /* safe and portable access using memcpy() */ + +static U16 LZ4_read16(const void* memPtr) +{ + U16 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; +} + +static U32 LZ4_read32(const void* memPtr) +{ + U32 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; +} + +static reg_t LZ4_read_ARCH(const void* memPtr) +{ + reg_t val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; +} + +static void LZ4_write16(void* memPtr, U16 value) +{ + LZ4_memcpy(memPtr, &value, sizeof(value)); +} + +static void LZ4_write32(void* memPtr, U32 value) +{ + LZ4_memcpy(memPtr, &value, sizeof(value)); +} + +#endif /* LZ4_FORCE_MEMORY_ACCESS */ + + +static U16 LZ4_readLE16(const void* memPtr) +{ + if (LZ4_isLittleEndian()) { + return LZ4_read16(memPtr); + } else { + const BYTE* p = (const BYTE*)memPtr; + return (U16)((U16)p[0] + (p[1]<<8)); + } +} + +static void LZ4_writeLE16(void* memPtr, U16 value) +{ + if (LZ4_isLittleEndian()) { + LZ4_write16(memPtr, value); + } else { + BYTE* p = (BYTE*)memPtr; + p[0] = (BYTE) value; + p[1] = (BYTE)(value>>8); + } +} + +/* customized variant of memcpy, which can overwrite up to 8 bytes beyond dstEnd */ +LZ4_FORCE_INLINE +void LZ4_wildCopy8(void* dstPtr, const void* srcPtr, void* dstEnd) +{ + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; + + do { LZ4_memcpy(d,s,8); d+=8; s+=8; } while (d= 16. */ +LZ4_FORCE_INLINE void +LZ4_wildCopy32(void* dstPtr, const void* srcPtr, void* dstEnd) +{ + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; + + do { LZ4_memcpy(d,s,16); LZ4_memcpy(d+16,s+16,16); d+=32; s+=32; } while (d= dstPtr + MINMATCH + * - there is at least 8 bytes available to write after dstEnd */ +LZ4_FORCE_INLINE void +LZ4_memcpy_using_offset(BYTE* dstPtr, const BYTE* srcPtr, BYTE* dstEnd, const size_t offset) +{ + BYTE v[8]; + + assert(dstEnd >= dstPtr + MINMATCH); + + switch(offset) { + case 1: + MEM_INIT(v, *srcPtr, 8); + break; + case 2: + LZ4_memcpy(v, srcPtr, 2); + LZ4_memcpy(&v[2], srcPtr, 2); +#if defined(_MSC_VER) && (_MSC_VER <= 1933) /* MSVC 2022 ver 17.3 or earlier */ +# pragma warning(push) +# pragma warning(disable : 6385) /* warning C6385: Reading invalid data from 'v'. */ +#endif + LZ4_memcpy(&v[4], v, 4); +#if defined(_MSC_VER) && (_MSC_VER <= 1933) /* MSVC 2022 ver 17.3 or earlier */ +# pragma warning(pop) +#endif + break; + case 4: + LZ4_memcpy(v, srcPtr, 4); + LZ4_memcpy(&v[4], srcPtr, 4); + break; + default: + LZ4_memcpy_using_offset_base(dstPtr, srcPtr, dstEnd, offset); + return; + } + + LZ4_memcpy(dstPtr, v, 8); + dstPtr += 8; + while (dstPtr < dstEnd) { + LZ4_memcpy(dstPtr, v, 8); + dstPtr += 8; + } +} +#endif + + +/*-************************************ +* Common functions +**************************************/ +static unsigned LZ4_NbCommonBytes (reg_t val) +{ + assert(val != 0); + if (LZ4_isLittleEndian()) { + if (sizeof(val) == 8) { +# if defined(_MSC_VER) && (_MSC_VER >= 1800) && (defined(_M_AMD64) && !defined(_M_ARM64EC)) && !defined(LZ4_FORCE_SW_BITCOUNT) +/*-************************************************************************************************* +* ARM64EC is a Microsoft-designed ARM64 ABI compatible with AMD64 applications on ARM64 Windows 11. +* The ARM64EC ABI does not support AVX/AVX2/AVX512 instructions, nor their relevant intrinsics +* including _tzcnt_u64. Therefore, we need to neuter the _tzcnt_u64 code path for ARM64EC. +****************************************************************************************************/ +# if defined(__clang__) && (__clang_major__ < 10) + /* Avoid undefined clang-cl intrinsics issue. + * See https://github.com/lz4/lz4/pull/1017 for details. */ + return (unsigned)__builtin_ia32_tzcnt_u64(val) >> 3; +# else + /* x64 CPUS without BMI support interpret `TZCNT` as `REP BSF` */ + return (unsigned)_tzcnt_u64(val) >> 3; +# endif +# elif defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanForward64(&r, (U64)val); + return (unsigned)r >> 3; +# elif (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_ctzll((U64)val) >> 3; +# else + const U64 m = 0x0101010101010101ULL; + val ^= val - 1; + return (unsigned)(((U64)((val & (m - 1)) * m)) >> 56); +# endif + } else /* 32 bits */ { +# if defined(_MSC_VER) && (_MSC_VER >= 1400) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r; + _BitScanForward(&r, (U32)val); + return (unsigned)r >> 3; +# elif (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(__TINYC__) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_ctz((U32)val) >> 3; +# else + const U32 m = 0x01010101; + return (unsigned)((((val - 1) ^ val) & (m - 1)) * m) >> 24; +# endif + } + } else /* Big Endian CPU */ { + if (sizeof(val)==8) { +# if (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(__TINYC__) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_clzll((U64)val) >> 3; +# else +#if 1 + /* this method is probably faster, + * but adds a 128 bytes lookup table */ + static const unsigned char ctz7_tab[128] = { + 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + }; + U64 const mask = 0x0101010101010101ULL; + U64 const t = (((val >> 8) - mask) | val) & mask; + return ctz7_tab[(t * 0x0080402010080402ULL) >> 57]; +#else + /* this method doesn't consume memory space like the previous one, + * but it contains several branches, + * that may end up slowing execution */ + static const U32 by32 = sizeof(val)*4; /* 32 on 64 bits (goal), 16 on 32 bits. + Just to avoid some static analyzer complaining about shift by 32 on 32-bits target. + Note that this code path is never triggered in 32-bits mode. */ + unsigned r; + if (!(val>>by32)) { r=4; } else { r=0; val>>=by32; } + if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; } + r += (!val); + return r; +#endif +# endif + } else /* 32 bits */ { +# if (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_clz((U32)val) >> 3; +# else + val >>= 8; + val = ((((val + 0x00FFFF00) | 0x00FFFFFF) + val) | + (val + 0x00FF0000)) >> 24; + return (unsigned)val ^ 3; +# endif + } + } +} + + +#define STEPSIZE sizeof(reg_t) +LZ4_FORCE_INLINE +unsigned LZ4_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* pInLimit) +{ + const BYTE* const pStart = pIn; + + if (likely(pIn < pInLimit-(STEPSIZE-1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { + pIn+=STEPSIZE; pMatch+=STEPSIZE; + } else { + return LZ4_NbCommonBytes(diff); + } } + + while (likely(pIn < pInLimit-(STEPSIZE-1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { pIn+=STEPSIZE; pMatch+=STEPSIZE; continue; } + pIn += LZ4_NbCommonBytes(diff); + return (unsigned)(pIn - pStart); + } + + if ((STEPSIZE==8) && (pIn<(pInLimit-3)) && (LZ4_read32(pMatch) == LZ4_read32(pIn))) { pIn+=4; pMatch+=4; } + if ((pIn<(pInLimit-1)) && (LZ4_read16(pMatch) == LZ4_read16(pIn))) { pIn+=2; pMatch+=2; } + if ((pIn compression run slower on incompressible data */ + + +/*-************************************ +* Local Structures and types +**************************************/ +typedef enum { clearedTable = 0, byPtr, byU32, byU16 } tableType_t; + +/** + * This enum distinguishes several different modes of accessing previous + * content in the stream. + * + * - noDict : There is no preceding content. + * - withPrefix64k : Table entries up to ctx->dictSize before the current blob + * blob being compressed are valid and refer to the preceding + * content (of length ctx->dictSize), which is available + * contiguously preceding in memory the content currently + * being compressed. + * - usingExtDict : Like withPrefix64k, but the preceding content is somewhere + * else in memory, starting at ctx->dictionary with length + * ctx->dictSize. + * - usingDictCtx : Everything concerning the preceding content is + * in a separate context, pointed to by ctx->dictCtx. + * ctx->dictionary, ctx->dictSize, and table entries + * in the current context that refer to positions + * preceding the beginning of the current compression are + * ignored. Instead, ctx->dictCtx->dictionary and ctx->dictCtx + * ->dictSize describe the location and size of the preceding + * content, and matches are found by looking in the ctx + * ->dictCtx->hashTable. + */ +typedef enum { noDict = 0, withPrefix64k, usingExtDict, usingDictCtx } dict_directive; +typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive; + + +/*-************************************ +* Local Utils +**************************************/ +int LZ4_versionNumber (void) { return LZ4_VERSION_NUMBER; } +const char* LZ4_versionString(void) { return LZ4_VERSION_STRING; } +int LZ4_compressBound(int isize) { return LZ4_COMPRESSBOUND(isize); } +int LZ4_sizeofState(void) { return sizeof(LZ4_stream_t); } + + +/*-**************************************** +* Internal Definitions, used only in Tests +*******************************************/ +#if defined (__cplusplus) +extern "C" { +#endif + +int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize); + +int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, + int compressedSize, int maxOutputSize, + const void* dictStart, size_t dictSize); +int LZ4_decompress_safe_partial_forceExtDict(const char* source, char* dest, + int compressedSize, int targetOutputSize, int dstCapacity, + const void* dictStart, size_t dictSize); +#if defined (__cplusplus) +} +#endif + +/*-****************************** +* Compression functions +********************************/ +LZ4_FORCE_INLINE U32 LZ4_hash4(U32 sequence, tableType_t const tableType) +{ + if (tableType == byU16) + return ((sequence * 2654435761U) >> ((MINMATCH*8)-(LZ4_HASHLOG+1))); + else + return ((sequence * 2654435761U) >> ((MINMATCH*8)-LZ4_HASHLOG)); +} + +LZ4_FORCE_INLINE U32 LZ4_hash5(U64 sequence, tableType_t const tableType) +{ + const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG; + if (LZ4_isLittleEndian()) { + const U64 prime5bytes = 889523592379ULL; + return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog)); + } else { + const U64 prime8bytes = 11400714785074694791ULL; + return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog)); + } +} + +LZ4_FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType) +{ + if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType); + return LZ4_hash4(LZ4_read32(p), tableType); +} + +LZ4_FORCE_INLINE void LZ4_clearHash(U32 h, void* tableBase, tableType_t const tableType) +{ + switch (tableType) + { + default: /* fallthrough */ + case clearedTable: { /* illegal! */ assert(0); return; } + case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = NULL; return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = 0; return; } + case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = 0; return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putIndexOnHash(U32 idx, U32 h, void* tableBase, tableType_t const tableType) +{ + switch (tableType) + { + default: /* fallthrough */ + case clearedTable: /* fallthrough */ + case byPtr: { /* illegal! */ assert(0); return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = idx; return; } + case byU16: { U16* hashTable = (U16*) tableBase; assert(idx < 65536); hashTable[h] = (U16)idx; return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putPositionOnHash(const BYTE* p, U32 h, + void* tableBase, tableType_t const tableType, + const BYTE* srcBase) +{ + switch (tableType) + { + case clearedTable: { /* illegal! */ assert(0); return; } + case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = p; return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = (U32)(p-srcBase); return; } + case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = (U16)(p-srcBase); return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + LZ4_putPositionOnHash(p, h, tableBase, tableType, srcBase); +} + +/* LZ4_getIndexOnHash() : + * Index of match position registered in hash table. + * hash position must be calculated by using base+index, or dictBase+index. + * Assumption 1 : only valid if tableType == byU32 or byU16. + * Assumption 2 : h is presumed valid (within limits of hash table) + */ +LZ4_FORCE_INLINE U32 LZ4_getIndexOnHash(U32 h, const void* tableBase, tableType_t tableType) +{ + LZ4_STATIC_ASSERT(LZ4_MEMORY_USAGE > 2); + if (tableType == byU32) { + const U32* const hashTable = (const U32*) tableBase; + assert(h < (1U << (LZ4_MEMORY_USAGE-2))); + return hashTable[h]; + } + if (tableType == byU16) { + const U16* const hashTable = (const U16*) tableBase; + assert(h < (1U << (LZ4_MEMORY_USAGE-1))); + return hashTable[h]; + } + assert(0); return 0; /* forbidden case */ +} + +static const BYTE* LZ4_getPositionOnHash(U32 h, const void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + if (tableType == byPtr) { const BYTE* const* hashTable = (const BYTE* const*) tableBase; return hashTable[h]; } + if (tableType == byU32) { const U32* const hashTable = (const U32*) tableBase; return hashTable[h] + srcBase; } + { const U16* const hashTable = (const U16*) tableBase; return hashTable[h] + srcBase; } /* default, to ensure a return */ +} + +LZ4_FORCE_INLINE const BYTE* +LZ4_getPosition(const BYTE* p, + const void* tableBase, tableType_t tableType, + const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + return LZ4_getPositionOnHash(h, tableBase, tableType, srcBase); +} + +LZ4_FORCE_INLINE void +LZ4_prepareTable(LZ4_stream_t_internal* const cctx, + const int inputSize, + const tableType_t tableType) { + /* If the table hasn't been used, it's guaranteed to be zeroed out, and is + * therefore safe to use no matter what mode we're in. Otherwise, we figure + * out if it's safe to leave as is or whether it needs to be reset. + */ + if ((tableType_t)cctx->tableType != clearedTable) { + assert(inputSize >= 0); + if ((tableType_t)cctx->tableType != tableType + || ((tableType == byU16) && cctx->currentOffset + (unsigned)inputSize >= 0xFFFFU) + || ((tableType == byU32) && cctx->currentOffset > 1 GB) + || tableType == byPtr + || inputSize >= 4 KB) + { + DEBUGLOG(4, "LZ4_prepareTable: Resetting table in %p", cctx); + MEM_INIT(cctx->hashTable, 0, LZ4_HASHTABLESIZE); + cctx->currentOffset = 0; + cctx->tableType = (U32)clearedTable; + } else { + DEBUGLOG(4, "LZ4_prepareTable: Re-use hash table (no reset)"); + } + } + + /* Adding a gap, so all previous entries are > LZ4_DISTANCE_MAX back, + * is faster than compressing without a gap. + * However, compressing with currentOffset == 0 is faster still, + * so we preserve that case. + */ + if (cctx->currentOffset != 0 && tableType == byU32) { + DEBUGLOG(5, "LZ4_prepareTable: adding 64KB to currentOffset"); + cctx->currentOffset += 64 KB; + } + + /* Finally, clear history */ + cctx->dictCtx = NULL; + cctx->dictionary = NULL; + cctx->dictSize = 0; +} + +/** LZ4_compress_generic() : + * inlined, to ensure branches are decided at compilation time. + * Presumed already validated at this stage: + * - source != NULL + * - inputSize > 0 + */ +LZ4_FORCE_INLINE int LZ4_compress_generic_validated( + LZ4_stream_t_internal* const cctx, + const char* const source, + char* const dest, + const int inputSize, + int* inputConsumed, /* only written when outputDirective == fillOutput */ + const int maxOutputSize, + const limitedOutput_directive outputDirective, + const tableType_t tableType, + const dict_directive dictDirective, + const dictIssue_directive dictIssue, + const int acceleration) +{ + int result; + const BYTE* ip = (const BYTE*) source; + + U32 const startIndex = cctx->currentOffset; + const BYTE* base = (const BYTE*) source - startIndex; + const BYTE* lowLimit; + + const LZ4_stream_t_internal* dictCtx = (const LZ4_stream_t_internal*) cctx->dictCtx; + const BYTE* const dictionary = + dictDirective == usingDictCtx ? dictCtx->dictionary : cctx->dictionary; + const U32 dictSize = + dictDirective == usingDictCtx ? dictCtx->dictSize : cctx->dictSize; + const U32 dictDelta = (dictDirective == usingDictCtx) ? startIndex - dictCtx->currentOffset : 0; /* make indexes in dictCtx comparable with index in current context */ + + int const maybe_extMem = (dictDirective == usingExtDict) || (dictDirective == usingDictCtx); + U32 const prefixIdxLimit = startIndex - dictSize; /* used when dictDirective == dictSmall */ + const BYTE* const dictEnd = dictionary ? dictionary + dictSize : dictionary; + const BYTE* anchor = (const BYTE*) source; + const BYTE* const iend = ip + inputSize; + const BYTE* const mflimitPlusOne = iend - MFLIMIT + 1; + const BYTE* const matchlimit = iend - LASTLITERALS; + + /* the dictCtx currentOffset is indexed on the start of the dictionary, + * while a dictionary in the current context precedes the currentOffset */ + const BYTE* dictBase = (dictionary == NULL) ? NULL : + (dictDirective == usingDictCtx) ? + dictionary + dictSize - dictCtx->currentOffset : + dictionary + dictSize - startIndex; + + BYTE* op = (BYTE*) dest; + BYTE* const olimit = op + maxOutputSize; + + U32 offset = 0; + U32 forwardH; + + DEBUGLOG(5, "LZ4_compress_generic_validated: srcSize=%i, tableType=%u", inputSize, tableType); + assert(ip != NULL); + /* If init conditions are not met, we don't have to mark stream + * as having dirty context, since no action was taken yet */ + if (outputDirective == fillOutput && maxOutputSize < 1) { return 0; } /* Impossible to store anything */ + if ((tableType == byU16) && (inputSize>=LZ4_64Klimit)) { return 0; } /* Size too large (not within 64K limit) */ + if (tableType==byPtr) assert(dictDirective==noDict); /* only supported use case with byPtr */ + assert(acceleration >= 1); + + lowLimit = (const BYTE*)source - (dictDirective == withPrefix64k ? dictSize : 0); + + /* Update context state */ + if (dictDirective == usingDictCtx) { + /* Subsequent linked blocks can't use the dictionary. */ + /* Instead, they use the block we just compressed. */ + cctx->dictCtx = NULL; + cctx->dictSize = (U32)inputSize; + } else { + cctx->dictSize += (U32)inputSize; + } + cctx->currentOffset += (U32)inputSize; + cctx->tableType = (U32)tableType; + + if (inputSizehashTable, tableType, base); + ip++; forwardH = LZ4_hashPosition(ip, tableType); + + /* Main Loop */ + for ( ; ; ) { + const BYTE* match; + BYTE* token; + const BYTE* filledIp; + + /* Find a match */ + if (tableType == byPtr) { + const BYTE* forwardIp = ip; + int step = 1; + int searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + assert(ip < mflimitPlusOne); + + match = LZ4_getPositionOnHash(h, cctx->hashTable, tableType, base); + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType, base); + + } while ( (match+LZ4_DISTANCE_MAX < ip) + || (LZ4_read32(match) != LZ4_read32(ip)) ); + + } else { /* byU32, byU16 */ + + const BYTE* forwardIp = ip; + int step = 1; + int searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + U32 const current = (U32)(forwardIp - base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex <= current); + assert(forwardIp - base < (ptrdiff_t)(2 GB - 1)); + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + assert(ip < mflimitPlusOne); + + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + assert(tableType == byU32); + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + matchIndex += dictDelta; /* make dictCtx index comparable with current context */ + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; + } + } else if (dictDirective == usingExtDict) { + if (matchIndex < startIndex) { + DEBUGLOG(7, "extDict candidate: matchIndex=%5u < startIndex=%5u", matchIndex, startIndex); + assert(startIndex - matchIndex >= MINMATCH); + assert(dictBase); + match = dictBase + matchIndex; + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; + } + } else { /* single continuous memory segment */ + match = base + matchIndex; + } + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + + DEBUGLOG(7, "candidate at pos=%u (offset=%u \n", matchIndex, current - matchIndex); + if ((dictIssue == dictSmall) && (matchIndex < prefixIdxLimit)) { continue; } /* match outside of valid area */ + assert(matchIndex < current); + if ( ((tableType != byU16) || (LZ4_DISTANCE_MAX < LZ4_DISTANCE_ABSOLUTE_MAX)) + && (matchIndex+LZ4_DISTANCE_MAX < current)) { + continue; + } /* too far */ + assert((current - matchIndex) <= LZ4_DISTANCE_MAX); /* match now expected within distance */ + + if (LZ4_read32(match) == LZ4_read32(ip)) { + if (maybe_extMem) offset = current - matchIndex; + break; /* match found */ + } + + } while(1); + } + + /* Catch up */ + filledIp = ip; + while (((ip>anchor) & (match > lowLimit)) && (unlikely(ip[-1]==match[-1]))) { ip--; match--; } + + /* Encode Literals */ + { unsigned const litLength = (unsigned)(ip - anchor); + token = op++; + if ((outputDirective == limitedOutput) && /* Check output buffer overflow */ + (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit)) ) { + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + if ((outputDirective == fillOutput) && + (unlikely(op + (litLength+240)/255 /* litlen */ + litLength /* literals */ + 2 /* offset */ + 1 /* token */ + MFLIMIT - MINMATCH /* min last literals so last match is <= end - MFLIMIT */ > olimit))) { + op--; + goto _last_literals; + } + if (litLength >= RUN_MASK) { + int len = (int)(litLength - RUN_MASK); + *token = (RUN_MASK<= 255 ; len-=255) *op++ = 255; + *op++ = (BYTE)len; + } + else *token = (BYTE)(litLength< olimit)) { + /* the match was too close to the end, rewind and go to last literals */ + op = token; + goto _last_literals; + } + + /* Encode Offset */ + if (maybe_extMem) { /* static test */ + DEBUGLOG(6, " with offset=%u (ext if > %i)", offset, (int)(ip - (const BYTE*)source)); + assert(offset <= LZ4_DISTANCE_MAX && offset > 0); + LZ4_writeLE16(op, (U16)offset); op+=2; + } else { + DEBUGLOG(6, " with offset=%u (same segment)", (U32)(ip - match)); + assert(ip-match <= LZ4_DISTANCE_MAX); + LZ4_writeLE16(op, (U16)(ip - match)); op+=2; + } + + /* Encode MatchLength */ + { unsigned matchCode; + + if ( (dictDirective==usingExtDict || dictDirective==usingDictCtx) + && (lowLimit==dictionary) /* match within extDict */ ) { + const BYTE* limit = ip + (dictEnd-match); + assert(dictEnd > match); + if (limit > matchlimit) limit = matchlimit; + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, limit); + ip += (size_t)matchCode + MINMATCH; + if (ip==limit) { + unsigned const more = LZ4_count(limit, (const BYTE*)source, matchlimit); + matchCode += more; + ip += more; + } + DEBUGLOG(6, " with matchLength=%u starting in extDict", matchCode+MINMATCH); + } else { + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, matchlimit); + ip += (size_t)matchCode + MINMATCH; + DEBUGLOG(6, " with matchLength=%u", matchCode+MINMATCH); + } + + if ((outputDirective) && /* Check output buffer overflow */ + (unlikely(op + (1 + LASTLITERALS) + (matchCode+240)/255 > olimit)) ) { + if (outputDirective == fillOutput) { + /* Match description too long : reduce it */ + U32 newMatchCode = 15 /* in token */ - 1 /* to avoid needing a zero byte */ + ((U32)(olimit - op) - 1 - LASTLITERALS) * 255; + ip -= matchCode - newMatchCode; + assert(newMatchCode < matchCode); + matchCode = newMatchCode; + if (unlikely(ip <= filledIp)) { + /* We have already filled up to filledIp so if ip ends up less than filledIp + * we have positions in the hash table beyond the current position. This is + * a problem if we reuse the hash table. So we have to remove these positions + * from the hash table. + */ + const BYTE* ptr; + DEBUGLOG(5, "Clearing %u positions", (U32)(filledIp - ip)); + for (ptr = ip; ptr <= filledIp; ++ptr) { + U32 const h = LZ4_hashPosition(ptr, tableType); + LZ4_clearHash(h, cctx->hashTable, tableType); + } + } + } else { + assert(outputDirective == limitedOutput); + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + } + if (matchCode >= ML_MASK) { + *token += ML_MASK; + matchCode -= ML_MASK; + LZ4_write32(op, 0xFFFFFFFF); + while (matchCode >= 4*255) { + op+=4; + LZ4_write32(op, 0xFFFFFFFF); + matchCode -= 4*255; + } + op += matchCode / 255; + *op++ = (BYTE)(matchCode % 255); + } else + *token += (BYTE)(matchCode); + } + /* Ensure we have enough space for the last literals. */ + assert(!(outputDirective == fillOutput && op + 1 + LASTLITERALS > olimit)); + + anchor = ip; + + /* Test end of chunk */ + if (ip >= mflimitPlusOne) break; + + /* Fill table */ + LZ4_putPosition(ip-2, cctx->hashTable, tableType, base); + + /* Test next position */ + if (tableType == byPtr) { + + match = LZ4_getPosition(ip, cctx->hashTable, tableType, base); + LZ4_putPosition(ip, cctx->hashTable, tableType, base); + if ( (match+LZ4_DISTANCE_MAX >= ip) + && (LZ4_read32(match) == LZ4_read32(ip)) ) + { token=op++; *token=0; goto _next_match; } + + } else { /* byU32, byU16 */ + + U32 const h = LZ4_hashPosition(ip, tableType); + U32 const current = (U32)(ip-base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex < current); + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + matchIndex += dictDelta; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; /* required for match length counter */ + } + } else if (dictDirective==usingExtDict) { + if (matchIndex < startIndex) { + assert(dictBase); + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; /* required for match length counter */ + } + } else { /* single memory segment */ + match = base + matchIndex; + } + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + assert(matchIndex < current); + if ( ((dictIssue==dictSmall) ? (matchIndex >= prefixIdxLimit) : 1) + && (((tableType==byU16) && (LZ4_DISTANCE_MAX == LZ4_DISTANCE_ABSOLUTE_MAX)) ? 1 : (matchIndex+LZ4_DISTANCE_MAX >= current)) + && (LZ4_read32(match) == LZ4_read32(ip)) ) { + token=op++; + *token=0; + if (maybe_extMem) offset = current - matchIndex; + DEBUGLOG(6, "seq.start:%i, literals=%u, match.start:%i", + (int)(anchor-(const BYTE*)source), 0, (int)(ip-(const BYTE*)source)); + goto _next_match; + } + } + + /* Prepare next loop */ + forwardH = LZ4_hashPosition(++ip, tableType); + + } + +_last_literals: + /* Encode Last Literals */ + { size_t lastRun = (size_t)(iend - anchor); + if ( (outputDirective) && /* Check output buffer overflow */ + (op + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > olimit)) { + if (outputDirective == fillOutput) { + /* adapt lastRun to fill 'dst' */ + assert(olimit >= op); + lastRun = (size_t)(olimit-op) - 1/*token*/; + lastRun -= (lastRun + 256 - RUN_MASK) / 256; /*additional length tokens*/ + } else { + assert(outputDirective == limitedOutput); + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + } + DEBUGLOG(6, "Final literal run : %i literals", (int)lastRun); + if (lastRun >= RUN_MASK) { + size_t accumulator = lastRun - RUN_MASK; + *op++ = RUN_MASK << ML_BITS; + for(; accumulator >= 255 ; accumulator-=255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRun< 0); + DEBUGLOG(5, "LZ4_compress_generic: compressed %i bytes into %i bytes", inputSize, result); + return result; +} + +/** LZ4_compress_generic() : + * inlined, to ensure branches are decided at compilation time; + * takes care of src == (NULL, 0) + * and forward the rest to LZ4_compress_generic_validated */ +LZ4_FORCE_INLINE int LZ4_compress_generic( + LZ4_stream_t_internal* const cctx, + const char* const src, + char* const dst, + const int srcSize, + int *inputConsumed, /* only written when outputDirective == fillOutput */ + const int dstCapacity, + const limitedOutput_directive outputDirective, + const tableType_t tableType, + const dict_directive dictDirective, + const dictIssue_directive dictIssue, + const int acceleration) +{ + DEBUGLOG(5, "LZ4_compress_generic: srcSize=%i, dstCapacity=%i", + srcSize, dstCapacity); + + if ((U32)srcSize > (U32)LZ4_MAX_INPUT_SIZE) { return 0; } /* Unsupported srcSize, too large (or negative) */ + if (srcSize == 0) { /* src == NULL supported if srcSize == 0 */ + if (outputDirective != notLimited && dstCapacity <= 0) return 0; /* no output, can't write anything */ + DEBUGLOG(5, "Generating an empty block"); + assert(outputDirective == notLimited || dstCapacity >= 1); + assert(dst != NULL); + dst[0] = 0; + if (outputDirective == fillOutput) { + assert (inputConsumed != NULL); + *inputConsumed = 0; + } + return 1; + } + assert(src != NULL); + + return LZ4_compress_generic_validated(cctx, src, dst, srcSize, + inputConsumed, /* only written into if outputDirective == fillOutput */ + dstCapacity, outputDirective, + tableType, dictDirective, dictIssue, acceleration); +} + + +int LZ4_compress_fast_extState(void* state, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + LZ4_stream_t_internal* const ctx = & LZ4_initStream(state, sizeof(LZ4_stream_t)) -> internal_donotuse; + assert(ctx != NULL); + if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT; + if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX; + if (maxOutputSize >= LZ4_compressBound(inputSize)) { + if (inputSize < LZ4_64Klimit) { + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (inputSize < LZ4_64Klimit) { + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } +} + +/** + * LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. It is only safe + * to call if the state buffer is known to be correctly initialized already + * (see comment in lz4.h on LZ4_resetStream_fast() for a definition of + * "correctly initialized"). + */ +int LZ4_compress_fast_extState_fastReset(void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration) +{ + LZ4_stream_t_internal* ctx = &((LZ4_stream_t*)state)->internal_donotuse; + if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT; + if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX; + + if (dstCapacity >= LZ4_compressBound(srcSize)) { + if (srcSize < LZ4_64Klimit) { + const tableType_t tableType = byU16; + LZ4_prepareTable(ctx, srcSize, tableType); + if (ctx->currentOffset) { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, dictSmall, acceleration); + } else { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + LZ4_prepareTable(ctx, srcSize, tableType); + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (srcSize < LZ4_64Klimit) { + const tableType_t tableType = byU16; + LZ4_prepareTable(ctx, srcSize, tableType); + if (ctx->currentOffset) { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, dictSmall, acceleration); + } else { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + LZ4_prepareTable(ctx, srcSize, tableType); + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } +} + + +int LZ4_compress_fast(const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + int result; +#if (LZ4_HEAPMODE) + LZ4_stream_t* ctxPtr = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + if (ctxPtr == NULL) return 0; +#else + LZ4_stream_t ctx; + LZ4_stream_t* const ctxPtr = &ctx; +#endif + result = LZ4_compress_fast_extState(ctxPtr, source, dest, inputSize, maxOutputSize, acceleration); + +#if (LZ4_HEAPMODE) + FREEMEM(ctxPtr); +#endif + return result; +} + + +int LZ4_compress_default(const char* src, char* dst, int srcSize, int maxOutputSize) +{ + return LZ4_compress_fast(src, dst, srcSize, maxOutputSize, 1); +} + + +/* Note!: This function leaves the stream in an unclean/broken state! + * It is not safe to subsequently use the same state with a _fastReset() or + * _continue() call without resetting it. */ +static int LZ4_compress_destSize_extState (LZ4_stream_t* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize) +{ + void* const s = LZ4_initStream(state, sizeof (*state)); + assert(s != NULL); (void)s; + + if (targetDstSize >= LZ4_compressBound(*srcSizePtr)) { /* compression success is guaranteed */ + return LZ4_compress_fast_extState(state, src, dst, *srcSizePtr, targetDstSize, 1); + } else { + if (*srcSizePtr < LZ4_64Klimit) { + return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, byU16, noDict, noDictIssue, 1); + } else { + tableType_t const addrMode = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, addrMode, noDict, noDictIssue, 1); + } } +} + + +int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize) +{ +#if (LZ4_HEAPMODE) + LZ4_stream_t* ctx = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + if (ctx == NULL) return 0; +#else + LZ4_stream_t ctxBody; + LZ4_stream_t* ctx = &ctxBody; +#endif + + int result = LZ4_compress_destSize_extState(ctx, src, dst, srcSizePtr, targetDstSize); + +#if (LZ4_HEAPMODE) + FREEMEM(ctx); +#endif + return result; +} + + + +/*-****************************** +* Streaming functions +********************************/ + +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +LZ4_stream_t* LZ4_createStream(void) +{ + LZ4_stream_t* const lz4s = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); + LZ4_STATIC_ASSERT(sizeof(LZ4_stream_t) >= sizeof(LZ4_stream_t_internal)); + DEBUGLOG(4, "LZ4_createStream %p", lz4s); + if (lz4s == NULL) return NULL; + LZ4_initStream(lz4s, sizeof(*lz4s)); + return lz4s; +} +#endif + +static size_t LZ4_stream_t_alignment(void) +{ +#if LZ4_ALIGN_TEST + typedef struct { char c; LZ4_stream_t t; } t_a; + return sizeof(t_a) - sizeof(LZ4_stream_t); +#else + return 1; /* effectively disabled */ +#endif +} + +LZ4_stream_t* LZ4_initStream (void* buffer, size_t size) +{ + DEBUGLOG(5, "LZ4_initStream"); + if (buffer == NULL) { return NULL; } + if (size < sizeof(LZ4_stream_t)) { return NULL; } + if (!LZ4_isAligned(buffer, LZ4_stream_t_alignment())) return NULL; + MEM_INIT(buffer, 0, sizeof(LZ4_stream_t_internal)); + return (LZ4_stream_t*)buffer; +} + +/* resetStream is now deprecated, + * prefer initStream() which is more general */ +void LZ4_resetStream (LZ4_stream_t* LZ4_stream) +{ + DEBUGLOG(5, "LZ4_resetStream (ctx:%p)", LZ4_stream); + MEM_INIT(LZ4_stream, 0, sizeof(LZ4_stream_t_internal)); +} + +void LZ4_resetStream_fast(LZ4_stream_t* ctx) { + LZ4_prepareTable(&(ctx->internal_donotuse), 0, byU32); +} + +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +int LZ4_freeStream (LZ4_stream_t* LZ4_stream) +{ + if (!LZ4_stream) return 0; /* support free on NULL */ + DEBUGLOG(5, "LZ4_freeStream %p", LZ4_stream); + FREEMEM(LZ4_stream); + return (0); +} +#endif + + +#define HASH_UNIT sizeof(reg_t) +int LZ4_loadDict (LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize) +{ + LZ4_stream_t_internal* dict = &LZ4_dict->internal_donotuse; + const tableType_t tableType = byU32; + const BYTE* p = (const BYTE*)dictionary; + const BYTE* const dictEnd = p + dictSize; + const BYTE* base; + + DEBUGLOG(4, "LZ4_loadDict (%i bytes from %p into %p)", dictSize, dictionary, LZ4_dict); + + /* It's necessary to reset the context, + * and not just continue it with prepareTable() + * to avoid any risk of generating overflowing matchIndex + * when compressing using this dictionary */ + LZ4_resetStream(LZ4_dict); + + /* We always increment the offset by 64 KB, since, if the dict is longer, + * we truncate it to the last 64k, and if it's shorter, we still want to + * advance by a whole window length so we can provide the guarantee that + * there are only valid offsets in the window, which allows an optimization + * in LZ4_compress_fast_continue() where it uses noDictIssue even when the + * dictionary isn't a full 64k. */ + dict->currentOffset += 64 KB; + + if (dictSize < (int)HASH_UNIT) { + return 0; + } + + if ((dictEnd - p) > 64 KB) p = dictEnd - 64 KB; + base = dictEnd - dict->currentOffset; + dict->dictionary = p; + dict->dictSize = (U32)(dictEnd - p); + dict->tableType = (U32)tableType; + + while (p <= dictEnd-HASH_UNIT) { + LZ4_putPosition(p, dict->hashTable, tableType, base); + p+=3; + } + + return (int)dict->dictSize; +} + +void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dictionaryStream) +{ + const LZ4_stream_t_internal* dictCtx = (dictionaryStream == NULL) ? NULL : + &(dictionaryStream->internal_donotuse); + + DEBUGLOG(4, "LZ4_attach_dictionary (%p, %p, size %u)", + workingStream, dictionaryStream, + dictCtx != NULL ? dictCtx->dictSize : 0); + + if (dictCtx != NULL) { + /* If the current offset is zero, we will never look in the + * external dictionary context, since there is no value a table + * entry can take that indicate a miss. In that case, we need + * to bump the offset to something non-zero. + */ + if (workingStream->internal_donotuse.currentOffset == 0) { + workingStream->internal_donotuse.currentOffset = 64 KB; + } + + /* Don't actually attach an empty dictionary. + */ + if (dictCtx->dictSize == 0) { + dictCtx = NULL; + } + } + workingStream->internal_donotuse.dictCtx = dictCtx; +} + + +static void LZ4_renormDictT(LZ4_stream_t_internal* LZ4_dict, int nextSize) +{ + assert(nextSize >= 0); + if (LZ4_dict->currentOffset + (unsigned)nextSize > 0x80000000) { /* potential ptrdiff_t overflow (32-bits mode) */ + /* rescale hash table */ + U32 const delta = LZ4_dict->currentOffset - 64 KB; + const BYTE* dictEnd = LZ4_dict->dictionary + LZ4_dict->dictSize; + int i; + DEBUGLOG(4, "LZ4_renormDictT"); + for (i=0; ihashTable[i] < delta) LZ4_dict->hashTable[i]=0; + else LZ4_dict->hashTable[i] -= delta; + } + LZ4_dict->currentOffset = 64 KB; + if (LZ4_dict->dictSize > 64 KB) LZ4_dict->dictSize = 64 KB; + LZ4_dict->dictionary = dictEnd - LZ4_dict->dictSize; + } +} + + +int LZ4_compress_fast_continue (LZ4_stream_t* LZ4_stream, + const char* source, char* dest, + int inputSize, int maxOutputSize, + int acceleration) +{ + const tableType_t tableType = byU32; + LZ4_stream_t_internal* const streamPtr = &LZ4_stream->internal_donotuse; + const char* dictEnd = streamPtr->dictSize ? (const char*)streamPtr->dictionary + streamPtr->dictSize : NULL; + + DEBUGLOG(5, "LZ4_compress_fast_continue (inputSize=%i, dictSize=%u)", inputSize, streamPtr->dictSize); + + LZ4_renormDictT(streamPtr, inputSize); /* fix index overflow */ + if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT; + if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX; + + /* invalidate tiny dictionaries */ + if ( (streamPtr->dictSize < 4) /* tiny dictionary : not enough for a hash */ + && (dictEnd != source) /* prefix mode */ + && (inputSize > 0) /* tolerance : don't lose history, in case next invocation would use prefix mode */ + && (streamPtr->dictCtx == NULL) /* usingDictCtx */ + ) { + DEBUGLOG(5, "LZ4_compress_fast_continue: dictSize(%u) at addr:%p is too small", streamPtr->dictSize, streamPtr->dictionary); + /* remove dictionary existence from history, to employ faster prefix mode */ + streamPtr->dictSize = 0; + streamPtr->dictionary = (const BYTE*)source; + dictEnd = source; + } + + /* Check overlapping input/dictionary space */ + { const char* const sourceEnd = source + inputSize; + if ((sourceEnd > (const char*)streamPtr->dictionary) && (sourceEnd < dictEnd)) { + streamPtr->dictSize = (U32)(dictEnd - sourceEnd); + if (streamPtr->dictSize > 64 KB) streamPtr->dictSize = 64 KB; + if (streamPtr->dictSize < 4) streamPtr->dictSize = 0; + streamPtr->dictionary = (const BYTE*)dictEnd - streamPtr->dictSize; + } + } + + /* prefix mode : source data follows dictionary */ + if (dictEnd == source) { + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) + return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, dictSmall, acceleration); + else + return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, noDictIssue, acceleration); + } + + /* external dictionary mode */ + { int result; + if (streamPtr->dictCtx) { + /* We depend here on the fact that dictCtx'es (produced by + * LZ4_loadDict) guarantee that their tables contain no references + * to offsets between dictCtx->currentOffset - 64 KB and + * dictCtx->currentOffset - dictCtx->dictSize. This makes it safe + * to use noDictIssue even when the dict isn't a full 64 KB. + */ + if (inputSize > 4 KB) { + /* For compressing large blobs, it is faster to pay the setup + * cost to copy the dictionary's tables into the active context, + * so that the compression loop is only looking into one table. + */ + LZ4_memcpy(streamPtr, streamPtr->dictCtx, sizeof(*streamPtr)); + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingDictCtx, noDictIssue, acceleration); + } + } else { /* small data <= 4 KB */ + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, dictSmall, acceleration); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); + } + } + streamPtr->dictionary = (const BYTE*)source; + streamPtr->dictSize = (U32)inputSize; + return result; + } +} + + +/* Hidden debug function, to force-test external dictionary mode */ +int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize) +{ + LZ4_stream_t_internal* streamPtr = &LZ4_dict->internal_donotuse; + int result; + + LZ4_renormDictT(streamPtr, srcSize); + + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { + result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, dictSmall, 1); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, noDictIssue, 1); + } + + streamPtr->dictionary = (const BYTE*)source; + streamPtr->dictSize = (U32)srcSize; + + return result; +} + + +/*! LZ4_saveDict() : + * If previously compressed data block is not guaranteed to remain available at its memory location, + * save it into a safer place (char* safeBuffer). + * Note : no need to call LZ4_loadDict() afterwards, dictionary is immediately usable, + * one can therefore call LZ4_compress_fast_continue() right after. + * @return : saved dictionary size in bytes (necessarily <= dictSize), or 0 if error. + */ +int LZ4_saveDict (LZ4_stream_t* LZ4_dict, char* safeBuffer, int dictSize) +{ + LZ4_stream_t_internal* const dict = &LZ4_dict->internal_donotuse; + + DEBUGLOG(5, "LZ4_saveDict : dictSize=%i, safeBuffer=%p", dictSize, safeBuffer); + + if ((U32)dictSize > 64 KB) { dictSize = 64 KB; } /* useless to define a dictionary > 64 KB */ + if ((U32)dictSize > dict->dictSize) { dictSize = (int)dict->dictSize; } + + if (safeBuffer == NULL) assert(dictSize == 0); + if (dictSize > 0) { + const BYTE* const previousDictEnd = dict->dictionary + dict->dictSize; + assert(dict->dictionary); + LZ4_memmove(safeBuffer, previousDictEnd - dictSize, (size_t)dictSize); + } + + dict->dictionary = (const BYTE*)safeBuffer; + dict->dictSize = (U32)dictSize; + + return dictSize; +} + + + +/*-******************************* + * Decompression functions + ********************************/ + +typedef enum { decode_full_block = 0, partial_decode = 1 } earlyEnd_directive; + +#undef MIN +#define MIN(a,b) ( (a) < (b) ? (a) : (b) ) + + +/* variant for decompress_unsafe() + * does not know end of input + * presumes input is well formed + * note : will consume at least one byte */ +size_t read_long_length_no_check(const BYTE** pp) +{ + size_t b, l = 0; + do { b = **pp; (*pp)++; l += b; } while (b==255); + DEBUGLOG(6, "read_long_length_no_check: +length=%zu using %zu input bytes", l, l/255 + 1) + return l; +} + +/* core decoder variant for LZ4_decompress_fast*() + * for legacy support only : these entry points are deprecated. + * - Presumes input is correctly formed (no defense vs malformed inputs) + * - Does not know input size (presume input buffer is "large enough") + * - Decompress a full block (only) + * @return : nb of bytes read from input. + * Note : this variant is not optimized for speed, just for maintenance. + * the goal is to remove support of decompress_fast*() variants by v2.0 +**/ +LZ4_FORCE_INLINE int +LZ4_decompress_unsafe_generic( + const BYTE* const istart, + BYTE* const ostart, + int decompressedSize, + + size_t prefixSize, + const BYTE* const dictStart, /* only if dict==usingExtDict */ + const size_t dictSize /* note: =0 if dictStart==NULL */ + ) +{ + const BYTE* ip = istart; + BYTE* op = (BYTE*)ostart; + BYTE* const oend = ostart + decompressedSize; + const BYTE* const prefixStart = ostart - prefixSize; + + DEBUGLOG(5, "LZ4_decompress_unsafe_generic"); + if (dictStart == NULL) assert(dictSize == 0); + + while (1) { + /* start new sequence */ + unsigned token = *ip++; + + /* literals */ + { size_t ll = token >> ML_BITS; + if (ll==15) { + /* long literal length */ + ll += read_long_length_no_check(&ip); + } + if ((size_t)(oend-op) < ll) return -1; /* output buffer overflow */ + LZ4_memmove(op, ip, ll); /* support in-place decompression */ + op += ll; + ip += ll; + if ((size_t)(oend-op) < MFLIMIT) { + if (op==oend) break; /* end of block */ + DEBUGLOG(5, "invalid: literals end at distance %zi from end of block", oend-op); + /* incorrect end of block : + * last match must start at least MFLIMIT==12 bytes before end of output block */ + return -1; + } } + + /* match */ + { size_t ml = token & 15; + size_t const offset = LZ4_readLE16(ip); + ip+=2; + + if (ml==15) { + /* long literal length */ + ml += read_long_length_no_check(&ip); + } + ml += MINMATCH; + + if ((size_t)(oend-op) < ml) return -1; /* output buffer overflow */ + + { const BYTE* match = op - offset; + + /* out of range */ + if (offset > (size_t)(op - prefixStart) + dictSize) { + DEBUGLOG(6, "offset out of range"); + return -1; + } + + /* check special case : extDict */ + if (offset > (size_t)(op - prefixStart)) { + /* extDict scenario */ + const BYTE* const dictEnd = dictStart + dictSize; + const BYTE* extMatch = dictEnd - (offset - (size_t)(op-prefixStart)); + size_t const extml = (size_t)(dictEnd - extMatch); + if (extml > ml) { + /* match entirely within extDict */ + LZ4_memmove(op, extMatch, ml); + op += ml; + ml = 0; + } else { + /* match split between extDict & prefix */ + LZ4_memmove(op, extMatch, extml); + op += extml; + ml -= extml; + } + match = prefixStart; + } + + /* match copy - slow variant, supporting overlap copy */ + { size_t u; + for (u=0; u= ipmax before start of loop. Returns initial_error if so. + * @error (output) - error code. Must be set to 0 before call. +**/ +typedef size_t Rvl_t; +static const Rvl_t rvl_error = (Rvl_t)(-1); +LZ4_FORCE_INLINE Rvl_t +read_variable_length(const BYTE** ip, const BYTE* ilimit, + int initial_check) +{ + Rvl_t s, length = 0; + assert(ip != NULL); + assert(*ip != NULL); + assert(ilimit != NULL); + if (initial_check && unlikely((*ip) >= ilimit)) { /* read limit reached */ + return rvl_error; + } + do { + s = **ip; + (*ip)++; + length += s; + if (unlikely((*ip) > ilimit)) { /* read limit reached */ + return rvl_error; + } + /* accumulator overflow detection (32-bit mode only) */ + if ((sizeof(length)<8) && unlikely(length > ((Rvl_t)(-1)/2)) ) { + return rvl_error; + } + } while (s==255); + + return length; +} + +/*! LZ4_decompress_generic() : + * This generic decompression function covers all use cases. + * It shall be instantiated several times, using different sets of directives. + * Note that it is important for performance that this function really get inlined, + * in order to remove useless branches during compilation optimization. + */ +LZ4_FORCE_INLINE int +LZ4_decompress_generic( + const char* const src, + char* const dst, + int srcSize, + int outputSize, /* If endOnInput==endOnInputSize, this value is `dstCapacity` */ + + earlyEnd_directive partialDecoding, /* full, partial */ + dict_directive dict, /* noDict, withPrefix64k, usingExtDict */ + const BYTE* const lowPrefix, /* always <= dst, == dst when no prefix */ + const BYTE* const dictStart, /* only if dict==usingExtDict */ + const size_t dictSize /* note : = 0 if noDict */ + ) +{ + if ((src == NULL) || (outputSize < 0)) { return -1; } + + { const BYTE* ip = (const BYTE*) src; + const BYTE* const iend = ip + srcSize; + + BYTE* op = (BYTE*) dst; + BYTE* const oend = op + outputSize; + BYTE* cpy; + + const BYTE* const dictEnd = (dictStart == NULL) ? NULL : dictStart + dictSize; + + const int checkOffset = (dictSize < (int)(64 KB)); + + + /* Set up the "end" pointers for the shortcut. */ + const BYTE* const shortiend = iend - 14 /*maxLL*/ - 2 /*offset*/; + const BYTE* const shortoend = oend - 14 /*maxLL*/ - 18 /*maxML*/; + + const BYTE* match; + size_t offset; + unsigned token; + size_t length; + + + DEBUGLOG(5, "LZ4_decompress_generic (srcSize:%i, dstSize:%i)", srcSize, outputSize); + + /* Special cases */ + assert(lowPrefix <= op); + if (unlikely(outputSize==0)) { + /* Empty output buffer */ + if (partialDecoding) return 0; + return ((srcSize==1) && (*ip==0)) ? 0 : -1; + } + if (unlikely(srcSize==0)) { return -1; } + + /* LZ4_FAST_DEC_LOOP: + * designed for modern OoO performance cpus, + * where copying reliably 32-bytes is preferable to an unpredictable branch. + * note : fast loop may show a regression for some client arm chips. */ +#if LZ4_FAST_DEC_LOOP + if ((oend - op) < FASTLOOP_SAFE_DISTANCE) { + DEBUGLOG(6, "skip fast decode loop"); + goto safe_decode; + } + + /* Fast loop : decode sequences as long as output < oend-FASTLOOP_SAFE_DISTANCE */ + while (1) { + /* Main fastloop assertion: We can always wildcopy FASTLOOP_SAFE_DISTANCE */ + assert(oend - op >= FASTLOOP_SAFE_DISTANCE); + assert(ip < iend); + token = *ip++; + length = token >> ML_BITS; /* literal length */ + + /* decode literal length */ + if (length == RUN_MASK) { + size_t const addl = read_variable_length(&ip, iend-RUN_MASK, 1); + if (addl == rvl_error) { goto _output_error; } + length += addl; + if (unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */ + if (unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */ + + /* copy literals */ + cpy = op+length; + LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); + if ((cpy>oend-32) || (ip+length>iend-32)) { goto safe_literal_copy; } + LZ4_wildCopy32(op, ip, cpy); + ip += length; op = cpy; + } else { + cpy = op+length; + DEBUGLOG(7, "copy %u bytes in a 16-bytes stripe", (unsigned)length); + /* We don't need to check oend, since we check it once for each loop below */ + if (ip > iend-(16 + 1/*max lit + offset + nextToken*/)) { goto safe_literal_copy; } + /* Literals can only be <= 14, but hope compilers optimize better when copy by a register size */ + LZ4_memcpy(op, ip, 16); + ip += length; op = cpy; + } + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + assert(match <= op); /* overflow check */ + + /* get matchlength */ + length = token & ML_MASK; + + if (length == ML_MASK) { + size_t const addl = read_variable_length(&ip, iend - LASTLITERALS + 1, 0); + if (addl == rvl_error) { goto _output_error; } + length += addl; + length += MINMATCH; + if (unlikely((uptrval)(op)+length<(uptrval)op)) { goto _output_error; } /* overflow detection */ + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) { goto _output_error; } /* Error : offset outside buffers */ + if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { + goto safe_match_copy; + } + } else { + length += MINMATCH; + if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { + goto safe_match_copy; + } + + /* Fastpath check: skip LZ4_wildCopy32 when true */ + if ((dict == withPrefix64k) || (match >= lowPrefix)) { + if (offset >= 8) { + assert(match >= lowPrefix); + assert(match <= op); + assert(op + 18 <= oend); + + LZ4_memcpy(op, match, 8); + LZ4_memcpy(op+8, match+8, 8); + LZ4_memcpy(op+16, match+16, 2); + op += length; + continue; + } } } + + if (checkOffset && (unlikely(match + dictSize < lowPrefix))) { goto _output_error; } /* Error : offset outside buffers */ + /* match starting within external dictionary */ + if ((dict==usingExtDict) && (match < lowPrefix)) { + assert(dictEnd != NULL); + if (unlikely(op+length > oend-LASTLITERALS)) { + if (partialDecoding) { + DEBUGLOG(7, "partialDecoding: dictionary match, close to dstEnd"); + length = MIN(length, (size_t)(oend-op)); + } else { + goto _output_error; /* end-of-block condition violated */ + } } + + if (length <= (size_t)(lowPrefix-match)) { + /* match fits entirely within external dictionary : just copy */ + LZ4_memmove(op, dictEnd - (lowPrefix-match), length); + op += length; + } else { + /* match stretches into both external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix - match); + size_t const restSize = length - copySize; + LZ4_memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while (op < endOfMatch) { *op++ = *copyFrom++; } + } else { + LZ4_memcpy(op, lowPrefix, restSize); + op += restSize; + } } + continue; + } + + /* copy match within block */ + cpy = op + length; + + assert((op <= oend) && (oend-op >= 32)); + if (unlikely(offset<16)) { + LZ4_memcpy_using_offset(op, match, cpy, offset); + } else { + LZ4_wildCopy32(op, match, cpy); + } + + op = cpy; /* wildcopy correction */ + } + safe_decode: +#endif + + /* Main Loop : decode remaining sequences where output < FASTLOOP_SAFE_DISTANCE */ + while (1) { + assert(ip < iend); + token = *ip++; + length = token >> ML_BITS; /* literal length */ + + /* A two-stage shortcut for the most common case: + * 1) If the literal length is 0..14, and there is enough space, + * enter the shortcut and copy 16 bytes on behalf of the literals + * (in the fast mode, only 8 bytes can be safely copied this way). + * 2) Further if the match length is 4..18, copy 18 bytes in a similar + * manner; but we ensure that there's enough space in the output for + * those 18 bytes earlier, upon entering the shortcut (in other words, + * there is a combined check for both stages). + */ + if ( (length != RUN_MASK) + /* strictly "less than" on input, to re-enter the loop with at least one byte */ + && likely((ip < shortiend) & (op <= shortoend)) ) { + /* Copy the literals */ + LZ4_memcpy(op, ip, 16); + op += length; ip += length; + + /* The second stage: prepare for match copying, decode full info. + * If it doesn't work out, the info won't be wasted. */ + length = token & ML_MASK; /* match length */ + offset = LZ4_readLE16(ip); ip += 2; + match = op - offset; + assert(match <= op); /* check overflow */ + + /* Do not deal with overlapping matches. */ + if ( (length != ML_MASK) + && (offset >= 8) + && (dict==withPrefix64k || match >= lowPrefix) ) { + /* Copy the match. */ + LZ4_memcpy(op + 0, match + 0, 8); + LZ4_memcpy(op + 8, match + 8, 8); + LZ4_memcpy(op +16, match +16, 2); + op += length + MINMATCH; + /* Both stages worked, load the next token. */ + continue; + } + + /* The second stage didn't work out, but the info is ready. + * Propel it right to the point of match copying. */ + goto _copy_match; + } + + /* decode literal length */ + if (length == RUN_MASK) { + size_t const addl = read_variable_length(&ip, iend-RUN_MASK, 1); + if (addl == rvl_error) { goto _output_error; } + length += addl; + if (unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */ + if (unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */ + } + + /* copy literals */ + cpy = op+length; +#if LZ4_FAST_DEC_LOOP + safe_literal_copy: +#endif + LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); + if ((cpy>oend-MFLIMIT) || (ip+length>iend-(2+1+LASTLITERALS))) { + /* We've either hit the input parsing restriction or the output parsing restriction. + * In the normal scenario, decoding a full block, it must be the last sequence, + * otherwise it's an error (invalid input or dimensions). + * In partialDecoding scenario, it's necessary to ensure there is no buffer overflow. + */ + if (partialDecoding) { + /* Since we are partial decoding we may be in this block because of the output parsing + * restriction, which is not valid since the output buffer is allowed to be undersized. + */ + DEBUGLOG(7, "partialDecoding: copying literals, close to input or output end") + DEBUGLOG(7, "partialDecoding: literal length = %u", (unsigned)length); + DEBUGLOG(7, "partialDecoding: remaining space in dstBuffer : %i", (int)(oend - op)); + DEBUGLOG(7, "partialDecoding: remaining space in srcBuffer : %i", (int)(iend - ip)); + /* Finishing in the middle of a literals segment, + * due to lack of input. + */ + if (ip+length > iend) { + length = (size_t)(iend-ip); + cpy = op + length; + } + /* Finishing in the middle of a literals segment, + * due to lack of output space. + */ + if (cpy > oend) { + cpy = oend; + assert(op<=oend); + length = (size_t)(oend-op); + } + } else { + /* We must be on the last sequence (or invalid) because of the parsing limitations + * so check that we exactly consume the input and don't overrun the output buffer. + */ + if ((ip+length != iend) || (cpy > oend)) { + DEBUGLOG(6, "should have been last run of literals") + DEBUGLOG(6, "ip(%p) + length(%i) = %p != iend (%p)", ip, (int)length, ip+length, iend); + DEBUGLOG(6, "or cpy(%p) > oend(%p)", cpy, oend); + goto _output_error; + } + } + LZ4_memmove(op, ip, length); /* supports overlapping memory regions, for in-place decompression scenarios */ + ip += length; + op += length; + /* Necessarily EOF when !partialDecoding. + * When partialDecoding, it is EOF if we've either + * filled the output buffer or + * can't proceed with reading an offset for following match. + */ + if (!partialDecoding || (cpy == oend) || (ip >= (iend-2))) { + break; + } + } else { + LZ4_wildCopy8(op, ip, cpy); /* can overwrite up to 8 bytes beyond cpy */ + ip += length; op = cpy; + } + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + + /* get matchlength */ + length = token & ML_MASK; + + _copy_match: + if (length == ML_MASK) { + size_t const addl = read_variable_length(&ip, iend - LASTLITERALS + 1, 0); + if (addl == rvl_error) { goto _output_error; } + length += addl; + if (unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error; /* overflow detection */ + } + length += MINMATCH; + +#if LZ4_FAST_DEC_LOOP + safe_match_copy: +#endif + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) goto _output_error; /* Error : offset outside buffers */ + /* match starting within external dictionary */ + if ((dict==usingExtDict) && (match < lowPrefix)) { + assert(dictEnd != NULL); + if (unlikely(op+length > oend-LASTLITERALS)) { + if (partialDecoding) length = MIN(length, (size_t)(oend-op)); + else goto _output_error; /* doesn't respect parsing restriction */ + } + + if (length <= (size_t)(lowPrefix-match)) { + /* match fits entirely within external dictionary : just copy */ + LZ4_memmove(op, dictEnd - (lowPrefix-match), length); + op += length; + } else { + /* match stretches into both external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix - match); + size_t const restSize = length - copySize; + LZ4_memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while (op < endOfMatch) *op++ = *copyFrom++; + } else { + LZ4_memcpy(op, lowPrefix, restSize); + op += restSize; + } } + continue; + } + assert(match >= lowPrefix); + + /* copy match within block */ + cpy = op + length; + + /* partialDecoding : may end anywhere within the block */ + assert(op<=oend); + if (partialDecoding && (cpy > oend-MATCH_SAFEGUARD_DISTANCE)) { + size_t const mlen = MIN(length, (size_t)(oend-op)); + const BYTE* const matchEnd = match + mlen; + BYTE* const copyEnd = op + mlen; + if (matchEnd > op) { /* overlap copy */ + while (op < copyEnd) { *op++ = *match++; } + } else { + LZ4_memcpy(op, match, mlen); + } + op = copyEnd; + if (op == oend) { break; } + continue; + } + + if (unlikely(offset<8)) { + LZ4_write32(op, 0); /* silence msan warning when offset==0 */ + op[0] = match[0]; + op[1] = match[1]; + op[2] = match[2]; + op[3] = match[3]; + match += inc32table[offset]; + LZ4_memcpy(op+4, match, 4); + match -= dec64table[offset]; + } else { + LZ4_memcpy(op, match, 8); + match += 8; + } + op += 8; + + if (unlikely(cpy > oend-MATCH_SAFEGUARD_DISTANCE)) { + BYTE* const oCopyLimit = oend - (WILDCOPYLENGTH-1); + if (cpy > oend-LASTLITERALS) { goto _output_error; } /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ + if (op < oCopyLimit) { + LZ4_wildCopy8(op, match, oCopyLimit); + match += oCopyLimit - op; + op = oCopyLimit; + } + while (op < cpy) { *op++ = *match++; } + } else { + LZ4_memcpy(op, match, 8); + if (length > 16) { LZ4_wildCopy8(op+8, match+8, cpy); } + } + op = cpy; /* wildcopy correction */ + } + + /* end of decoding */ + DEBUGLOG(5, "decoded %i bytes", (int) (((char*)op)-dst)); + return (int) (((char*)op)-dst); /* Nb of output bytes decoded */ + + /* Overflow error detected */ + _output_error: + return (int) (-(((const char*)ip)-src))-1; + } +} + + +/*===== Instantiate the API decoding functions. =====*/ + +LZ4_FORCE_O2 +int LZ4_decompress_safe(const char* source, char* dest, int compressedSize, int maxDecompressedSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, + decode_full_block, noDict, + (BYTE*)dest, NULL, 0); +} + +LZ4_FORCE_O2 +int LZ4_decompress_safe_partial(const char* src, char* dst, int compressedSize, int targetOutputSize, int dstCapacity) +{ + dstCapacity = MIN(targetOutputSize, dstCapacity); + return LZ4_decompress_generic(src, dst, compressedSize, dstCapacity, + partial_decode, + noDict, (BYTE*)dst, NULL, 0); +} + +LZ4_FORCE_O2 +int LZ4_decompress_fast(const char* source, char* dest, int originalSize) +{ + DEBUGLOG(5, "LZ4_decompress_fast"); + return LZ4_decompress_unsafe_generic( + (const BYTE*)source, (BYTE*)dest, originalSize, + 0, NULL, 0); +} + +/*===== Instantiate a few more decoding cases, used more than once. =====*/ + +LZ4_FORCE_O2 /* Exported, an obsolete API function. */ +int LZ4_decompress_safe_withPrefix64k(const char* source, char* dest, int compressedSize, int maxOutputSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + decode_full_block, withPrefix64k, + (BYTE*)dest - 64 KB, NULL, 0); +} + +LZ4_FORCE_O2 +static int LZ4_decompress_safe_partial_withPrefix64k(const char* source, char* dest, int compressedSize, int targetOutputSize, int dstCapacity) +{ + dstCapacity = MIN(targetOutputSize, dstCapacity); + return LZ4_decompress_generic(source, dest, compressedSize, dstCapacity, + partial_decode, withPrefix64k, + (BYTE*)dest - 64 KB, NULL, 0); +} + +/* Another obsolete API function, paired with the previous one. */ +int LZ4_decompress_fast_withPrefix64k(const char* source, char* dest, int originalSize) +{ + return LZ4_decompress_unsafe_generic( + (const BYTE*)source, (BYTE*)dest, originalSize, + 64 KB, NULL, 0); +} + +LZ4_FORCE_O2 +static int LZ4_decompress_safe_withSmallPrefix(const char* source, char* dest, int compressedSize, int maxOutputSize, + size_t prefixSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + decode_full_block, noDict, + (BYTE*)dest-prefixSize, NULL, 0); +} + +LZ4_FORCE_O2 +static int LZ4_decompress_safe_partial_withSmallPrefix(const char* source, char* dest, int compressedSize, int targetOutputSize, int dstCapacity, + size_t prefixSize) +{ + dstCapacity = MIN(targetOutputSize, dstCapacity); + return LZ4_decompress_generic(source, dest, compressedSize, dstCapacity, + partial_decode, noDict, + (BYTE*)dest-prefixSize, NULL, 0); +} + +LZ4_FORCE_O2 +int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, + int compressedSize, int maxOutputSize, + const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + decode_full_block, usingExtDict, + (BYTE*)dest, (const BYTE*)dictStart, dictSize); +} + +LZ4_FORCE_O2 +int LZ4_decompress_safe_partial_forceExtDict(const char* source, char* dest, + int compressedSize, int targetOutputSize, int dstCapacity, + const void* dictStart, size_t dictSize) +{ + dstCapacity = MIN(targetOutputSize, dstCapacity); + return LZ4_decompress_generic(source, dest, compressedSize, dstCapacity, + partial_decode, usingExtDict, + (BYTE*)dest, (const BYTE*)dictStart, dictSize); +} + +LZ4_FORCE_O2 +static int LZ4_decompress_fast_extDict(const char* source, char* dest, int originalSize, + const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_unsafe_generic( + (const BYTE*)source, (BYTE*)dest, originalSize, + 0, (const BYTE*)dictStart, dictSize); +} + +/* The "double dictionary" mode, for use with e.g. ring buffers: the first part + * of the dictionary is passed as prefix, and the second via dictStart + dictSize. + * These routines are used only once, in LZ4_decompress_*_continue(). + */ +LZ4_FORCE_INLINE +int LZ4_decompress_safe_doubleDict(const char* source, char* dest, int compressedSize, int maxOutputSize, + size_t prefixSize, const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + decode_full_block, usingExtDict, + (BYTE*)dest-prefixSize, (const BYTE*)dictStart, dictSize); +} + +/*===== streaming decompression functions =====*/ + +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +LZ4_streamDecode_t* LZ4_createStreamDecode(void) +{ + LZ4_STATIC_ASSERT(sizeof(LZ4_streamDecode_t) >= sizeof(LZ4_streamDecode_t_internal)); + return (LZ4_streamDecode_t*) ALLOC_AND_ZERO(sizeof(LZ4_streamDecode_t)); +} + +int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream) +{ + if (LZ4_stream == NULL) { return 0; } /* support free on NULL */ + FREEMEM(LZ4_stream); + return 0; +} +#endif + +/*! LZ4_setStreamDecode() : + * Use this function to instruct where to find the dictionary. + * This function is not necessary if previous data is still available where it was decoded. + * Loading a size of 0 is allowed (same effect as no dictionary). + * @return : 1 if OK, 0 if error + */ +int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + lz4sd->prefixSize = (size_t)dictSize; + if (dictSize) { + assert(dictionary != NULL); + lz4sd->prefixEnd = (const BYTE*) dictionary + dictSize; + } else { + lz4sd->prefixEnd = (const BYTE*) dictionary; + } + lz4sd->externalDict = NULL; + lz4sd->extDictSize = 0; + return 1; +} + +/*! LZ4_decoderRingBufferSize() : + * when setting a ring buffer for streaming decompression (optional scenario), + * provides the minimum size of this ring buffer + * to be compatible with any source respecting maxBlockSize condition. + * Note : in a ring buffer scenario, + * blocks are presumed decompressed next to each other. + * When not enough space remains for next block (remainingSize < maxBlockSize), + * decoding resumes from beginning of ring buffer. + * @return : minimum ring buffer size, + * or 0 if there is an error (invalid maxBlockSize). + */ +int LZ4_decoderRingBufferSize(int maxBlockSize) +{ + if (maxBlockSize < 0) return 0; + if (maxBlockSize > LZ4_MAX_INPUT_SIZE) return 0; + if (maxBlockSize < 16) maxBlockSize = 16; + return LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize); +} + +/* +*_continue() : + These decoding functions allow decompression of multiple blocks in "streaming" mode. + Previously decoded blocks must still be available at the memory position where they were decoded. + If it's not possible, save the relevant part of decoded data into a safe buffer, + and indicate where it stands using LZ4_setStreamDecode() +*/ +LZ4_FORCE_O2 +int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int compressedSize, int maxOutputSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + int result; + + if (lz4sd->prefixSize == 0) { + /* The first call, no dictionary yet. */ + assert(lz4sd->extDictSize == 0); + result = LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)result; + lz4sd->prefixEnd = (BYTE*)dest + result; + } else if (lz4sd->prefixEnd == (BYTE*)dest) { + /* They're rolling the current segment. */ + if (lz4sd->prefixSize >= 64 KB - 1) + result = LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize); + else if (lz4sd->extDictSize == 0) + result = LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, + lz4sd->prefixSize); + else + result = LZ4_decompress_safe_doubleDict(source, dest, compressedSize, maxOutputSize, + lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize += (size_t)result; + lz4sd->prefixEnd += result; + } else { + /* The buffer wraps around, or they're switching to another buffer. */ + lz4sd->extDictSize = lz4sd->prefixSize; + lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; + result = LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, + lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)result; + lz4sd->prefixEnd = (BYTE*)dest + result; + } + + return result; +} + +LZ4_FORCE_O2 int +LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, + const char* source, char* dest, int originalSize) +{ + LZ4_streamDecode_t_internal* const lz4sd = + (assert(LZ4_streamDecode!=NULL), &LZ4_streamDecode->internal_donotuse); + int result; + + DEBUGLOG(5, "LZ4_decompress_fast_continue (toDecodeSize=%i)", originalSize); + assert(originalSize >= 0); + + if (lz4sd->prefixSize == 0) { + DEBUGLOG(5, "first invocation : no prefix nor extDict"); + assert(lz4sd->extDictSize == 0); + result = LZ4_decompress_fast(source, dest, originalSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)originalSize; + lz4sd->prefixEnd = (BYTE*)dest + originalSize; + } else if (lz4sd->prefixEnd == (BYTE*)dest) { + DEBUGLOG(5, "continue using existing prefix"); + result = LZ4_decompress_unsafe_generic( + (const BYTE*)source, (BYTE*)dest, originalSize, + lz4sd->prefixSize, + lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize += (size_t)originalSize; + lz4sd->prefixEnd += originalSize; + } else { + DEBUGLOG(5, "prefix becomes extDict"); + lz4sd->extDictSize = lz4sd->prefixSize; + lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; + result = LZ4_decompress_fast_extDict(source, dest, originalSize, + lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)originalSize; + lz4sd->prefixEnd = (BYTE*)dest + originalSize; + } + + return result; +} + + +/* +Advanced decoding functions : +*_usingDict() : + These decoding functions work the same as "_continue" ones, + the dictionary must be explicitly provided within parameters +*/ + +int LZ4_decompress_safe_usingDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize) +{ + if (dictSize==0) + return LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize); + if (dictStart+dictSize == dest) { + if (dictSize >= 64 KB - 1) { + return LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize); + } + assert(dictSize >= 0); + return LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, (size_t)dictSize); + } + assert(dictSize >= 0); + return LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, dictStart, (size_t)dictSize); +} + +int LZ4_decompress_safe_partial_usingDict(const char* source, char* dest, int compressedSize, int targetOutputSize, int dstCapacity, const char* dictStart, int dictSize) +{ + if (dictSize==0) + return LZ4_decompress_safe_partial(source, dest, compressedSize, targetOutputSize, dstCapacity); + if (dictStart+dictSize == dest) { + if (dictSize >= 64 KB - 1) { + return LZ4_decompress_safe_partial_withPrefix64k(source, dest, compressedSize, targetOutputSize, dstCapacity); + } + assert(dictSize >= 0); + return LZ4_decompress_safe_partial_withSmallPrefix(source, dest, compressedSize, targetOutputSize, dstCapacity, (size_t)dictSize); + } + assert(dictSize >= 0); + return LZ4_decompress_safe_partial_forceExtDict(source, dest, compressedSize, targetOutputSize, dstCapacity, dictStart, (size_t)dictSize); +} + +int LZ4_decompress_fast_usingDict(const char* source, char* dest, int originalSize, const char* dictStart, int dictSize) +{ + if (dictSize==0 || dictStart+dictSize == dest) + return LZ4_decompress_unsafe_generic( + (const BYTE*)source, (BYTE*)dest, originalSize, + (size_t)dictSize, NULL, 0); + assert(dictSize >= 0); + return LZ4_decompress_fast_extDict(source, dest, originalSize, dictStart, (size_t)dictSize); +} + + +/*=************************************************* +* Obsolete Functions +***************************************************/ +/* obsolete compression functions */ +int LZ4_compress_limitedOutput(const char* source, char* dest, int inputSize, int maxOutputSize) +{ + return LZ4_compress_default(source, dest, inputSize, maxOutputSize); +} +int LZ4_compress(const char* src, char* dest, int srcSize) +{ + return LZ4_compress_default(src, dest, srcSize, LZ4_compressBound(srcSize)); +} +int LZ4_compress_limitedOutput_withState (void* state, const char* src, char* dst, int srcSize, int dstSize) +{ + return LZ4_compress_fast_extState(state, src, dst, srcSize, dstSize, 1); +} +int LZ4_compress_withState (void* state, const char* src, char* dst, int srcSize) +{ + return LZ4_compress_fast_extState(state, src, dst, srcSize, LZ4_compressBound(srcSize), 1); +} +int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_stream, const char* src, char* dst, int srcSize, int dstCapacity) +{ + return LZ4_compress_fast_continue(LZ4_stream, src, dst, srcSize, dstCapacity, 1); +} +int LZ4_compress_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize) +{ + return LZ4_compress_fast_continue(LZ4_stream, source, dest, inputSize, LZ4_compressBound(inputSize), 1); +} + +/* +These decompression functions are deprecated and should no longer be used. +They are only provided here for compatibility with older user programs. +- LZ4_uncompress is totally equivalent to LZ4_decompress_fast +- LZ4_uncompress_unknownOutputSize is totally equivalent to LZ4_decompress_safe +*/ +int LZ4_uncompress (const char* source, char* dest, int outputSize) +{ + return LZ4_decompress_fast(source, dest, outputSize); +} +int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize) +{ + return LZ4_decompress_safe(source, dest, isize, maxOutputSize); +} + +/* Obsolete Streaming functions */ + +int LZ4_sizeofStreamState(void) { return sizeof(LZ4_stream_t); } + +int LZ4_resetStreamState(void* state, char* inputBuffer) +{ + (void)inputBuffer; + LZ4_resetStream((LZ4_stream_t*)state); + return 0; +} + +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +void* LZ4_create (char* inputBuffer) +{ + (void)inputBuffer; + return LZ4_createStream(); +} +#endif + +char* LZ4_slideInputBuffer (void* state) +{ + /* avoid const char * -> char * conversion warning */ + return (char *)(uptrval)((LZ4_stream_t*)state)->internal_donotuse.dictionary; +} + +#endif /* LZ4_COMMONDEFS_ONLY */ diff --git a/contrib/tinyusdz/tinyusdz_repo/src/lz4/lz4.h b/contrib/tinyusdz/tinyusdz_repo/src/lz4/lz4.h new file mode 100644 index 000000000..491c6087c --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/lz4/lz4.h @@ -0,0 +1,842 @@ +/* + * LZ4 - Fast LZ compression algorithm + * Header File + * Copyright (C) 2011-2020, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/lz4/lz4 +*/ +#if defined (__cplusplus) +extern "C" { +#endif + +#ifndef LZ4_H_2983827168210 +#define LZ4_H_2983827168210 + +/* --- Dependency --- */ +#include /* size_t */ + + +/** + Introduction + + LZ4 is lossless compression algorithm, providing compression speed >500 MB/s per core, + scalable with multi-cores CPU. It features an extremely fast decoder, with speed in + multiple GB/s per core, typically reaching RAM speed limits on multi-core systems. + + The LZ4 compression library provides in-memory compression and decompression functions. + It gives full buffer control to user. + Compression can be done in: + - a single step (described as Simple Functions) + - a single step, reusing a context (described in Advanced Functions) + - unbounded multiple steps (described as Streaming compression) + + lz4.h generates and decodes LZ4-compressed blocks (doc/lz4_Block_format.md). + Decompressing such a compressed block requires additional metadata. + Exact metadata depends on exact decompression function. + For the typical case of LZ4_decompress_safe(), + metadata includes block's compressed size, and maximum bound of decompressed size. + Each application is free to encode and pass such metadata in whichever way it wants. + + lz4.h only handle blocks, it can not generate Frames. + + Blocks are different from Frames (doc/lz4_Frame_format.md). + Frames bundle both blocks and metadata in a specified manner. + Embedding metadata is required for compressed data to be self-contained and portable. + Frame format is delivered through a companion API, declared in lz4frame.h. + The `lz4` CLI can only manage frames. +*/ + +/*^*************************************************************** +* Export parameters +*****************************************************************/ +/* +* LZ4_DLL_EXPORT : +* Enable exporting of functions when building a Windows DLL +* LZ4LIB_VISIBILITY : +* Control library symbols visibility. +*/ +#ifndef LZ4LIB_VISIBILITY +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default"))) +# else +# define LZ4LIB_VISIBILITY +# endif +#endif +#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1) +# define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY +#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1) +# define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ +#else +# define LZ4LIB_API LZ4LIB_VISIBILITY +#endif + +/*! LZ4_FREESTANDING : + * When this macro is set to 1, it enables "freestanding mode" that is + * suitable for typical freestanding environment which doesn't support + * standard C library. + * + * - LZ4_FREESTANDING is a compile-time switch. + * - It requires the following macros to be defined: + * LZ4_memcpy, LZ4_memmove, LZ4_memset. + * - It only enables LZ4/HC functions which don't use heap. + * All LZ4F_* functions are not supported. + * - See tests/freestanding.c to check its basic setup. + */ +#if defined(LZ4_FREESTANDING) && (LZ4_FREESTANDING == 1) +# define LZ4_HEAPMODE 0 +# define LZ4HC_HEAPMODE 0 +# define LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION 1 +# if !defined(LZ4_memcpy) +# error "LZ4_FREESTANDING requires macro 'LZ4_memcpy'." +# endif +# if !defined(LZ4_memset) +# error "LZ4_FREESTANDING requires macro 'LZ4_memset'." +# endif +# if !defined(LZ4_memmove) +# error "LZ4_FREESTANDING requires macro 'LZ4_memmove'." +# endif +#elif ! defined(LZ4_FREESTANDING) +# define LZ4_FREESTANDING 0 +#endif + + +/*------ Version ------*/ +#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */ +#define LZ4_VERSION_MINOR 9 /* for new (non-breaking) interface capabilities */ +#define LZ4_VERSION_RELEASE 4 /* for tweaks, bug-fixes, or development */ + +#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) + +#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE +#define LZ4_QUOTE(str) #str +#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str) +#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) /* requires v1.7.3+ */ + +LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version; requires v1.3.0+ */ +LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; useful to check dll version; requires v1.7.5+ */ + + +/*-************************************ +* Tuning parameter +**************************************/ +#define LZ4_MEMORY_USAGE_MIN 10 +#define LZ4_MEMORY_USAGE_DEFAULT 14 +#define LZ4_MEMORY_USAGE_MAX 20 + +/*! + * LZ4_MEMORY_USAGE : + * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; ) + * Increasing memory usage improves compression ratio, at the cost of speed. + * Reduced memory usage may improve speed at the cost of ratio, thanks to better cache locality. + * Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache + */ +#ifndef LZ4_MEMORY_USAGE +# define LZ4_MEMORY_USAGE LZ4_MEMORY_USAGE_DEFAULT +#endif + +#if (LZ4_MEMORY_USAGE < LZ4_MEMORY_USAGE_MIN) +# error "LZ4_MEMORY_USAGE is too small !" +#endif + +#if (LZ4_MEMORY_USAGE > LZ4_MEMORY_USAGE_MAX) +# error "LZ4_MEMORY_USAGE is too large !" +#endif + +/*-************************************ +* Simple Functions +**************************************/ +/*! LZ4_compress_default() : + * Compresses 'srcSize' bytes from buffer 'src' + * into already allocated 'dst' buffer of size 'dstCapacity'. + * Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize). + * It also runs faster, so it's a recommended setting. + * If the function cannot compress 'src' into a more limited 'dst' budget, + * compression stops *immediately*, and the function result is zero. + * In which case, 'dst' content is undefined (invalid). + * srcSize : max supported value is LZ4_MAX_INPUT_SIZE. + * dstCapacity : size of buffer 'dst' (which must be already allocated) + * @return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity) + * or 0 if compression fails + * Note : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer). + */ +LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity); + +/*! LZ4_decompress_safe() : + * compressedSize : is the exact complete size of the compressed block. + * dstCapacity : is the size of destination buffer (which must be already allocated), presumed an upper bound of decompressed size. + * @return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity) + * If destination buffer is not large enough, decoding will stop and output an error code (negative value). + * If the source stream is detected malformed, the function will stop decoding and return a negative result. + * Note 1 : This function is protected against malicious data packets : + * it will never writes outside 'dst' buffer, nor read outside 'source' buffer, + * even if the compressed block is maliciously modified to order the decoder to do these actions. + * In such case, the decoder stops immediately, and considers the compressed block malformed. + * Note 2 : compressedSize and dstCapacity must be provided to the function, the compressed block does not contain them. + * The implementation is free to send / store / derive this information in whichever way is most beneficial. + * If there is a need for a different format which bundles together both compressed data and its metadata, consider looking at lz4frame.h instead. + */ +LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity); + + +/*-************************************ +* Advanced Functions +**************************************/ +#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */ +#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16) + +/*! LZ4_compressBound() : + Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible) + This function is primarily useful for memory allocation purposes (destination buffer size). + Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example). + Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize) + inputSize : max supported value is LZ4_MAX_INPUT_SIZE + return : maximum output size in a "worst case" scenario + or 0, if input size is incorrect (too large or negative) +*/ +LZ4LIB_API int LZ4_compressBound(int inputSize); + +/*! LZ4_compress_fast() : + Same as LZ4_compress_default(), but allows selection of "acceleration" factor. + The larger the acceleration value, the faster the algorithm, but also the lesser the compression. + It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed. + An acceleration value of "1" is the same as regular LZ4_compress_default() + Values <= 0 will be replaced by LZ4_ACCELERATION_DEFAULT (currently == 1, see lz4.c). + Values > LZ4_ACCELERATION_MAX will be replaced by LZ4_ACCELERATION_MAX (currently == 65537, see lz4.c). +*/ +LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + + +/*! LZ4_compress_fast_extState() : + * Same as LZ4_compress_fast(), using an externally allocated memory space for its state. + * Use LZ4_sizeofState() to know how much memory must be allocated, + * and allocate it on 8-bytes boundaries (using `malloc()` typically). + * Then, provide this buffer as `void* state` to compression function. + */ +LZ4LIB_API int LZ4_sizeofState(void); +LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + + +/*! LZ4_compress_destSize() : + * Reverse the logic : compresses as much data as possible from 'src' buffer + * into already allocated buffer 'dst', of size >= 'targetDestSize'. + * This function either compresses the entire 'src' content into 'dst' if it's large enough, + * or fill 'dst' buffer completely with as much data as possible from 'src'. + * note: acceleration parameter is fixed to "default". + * + * *srcSizePtr : will be modified to indicate how many bytes where read from 'src' to fill 'dst'. + * New value is necessarily <= input value. + * @return : Nb bytes written into 'dst' (necessarily <= targetDestSize) + * or 0 if compression fails. + * + * Note : from v1.8.2 to v1.9.1, this function had a bug (fixed un v1.9.2+): + * the produced compressed content could, in specific circumstances, + * require to be decompressed into a destination buffer larger + * by at least 1 byte than the content to decompress. + * If an application uses `LZ4_compress_destSize()`, + * it's highly recommended to update liblz4 to v1.9.2 or better. + * If this can't be done or ensured, + * the receiving decompression function should provide + * a dstCapacity which is > decompressedSize, by at least 1 byte. + * See https://github.com/lz4/lz4/issues/859 for details + */ +LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePtr, int targetDstSize); + + +/*! LZ4_decompress_safe_partial() : + * Decompress an LZ4 compressed block, of size 'srcSize' at position 'src', + * into destination buffer 'dst' of size 'dstCapacity'. + * Up to 'targetOutputSize' bytes will be decoded. + * The function stops decoding on reaching this objective. + * This can be useful to boost performance + * whenever only the beginning of a block is required. + * + * @return : the number of bytes decoded in `dst` (necessarily <= targetOutputSize) + * If source stream is detected malformed, function returns a negative result. + * + * Note 1 : @return can be < targetOutputSize, if compressed block contains less data. + * + * Note 2 : targetOutputSize must be <= dstCapacity + * + * Note 3 : this function effectively stops decoding on reaching targetOutputSize, + * so dstCapacity is kind of redundant. + * This is because in older versions of this function, + * decoding operation would still write complete sequences. + * Therefore, there was no guarantee that it would stop writing at exactly targetOutputSize, + * it could write more bytes, though only up to dstCapacity. + * Some "margin" used to be required for this operation to work properly. + * Thankfully, this is no longer necessary. + * The function nonetheless keeps the same signature, in an effort to preserve API compatibility. + * + * Note 4 : If srcSize is the exact size of the block, + * then targetOutputSize can be any value, + * including larger than the block's decompressed size. + * The function will, at most, generate block's decompressed size. + * + * Note 5 : If srcSize is _larger_ than block's compressed size, + * then targetOutputSize **MUST** be <= block's decompressed size. + * Otherwise, *silent corruption will occur*. + */ +LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity); + + +/*-********************************************* +* Streaming Compression Functions +***********************************************/ +typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */ + +/** + Note about RC_INVOKED + + - RC_INVOKED is predefined symbol of rc.exe (the resource compiler which is part of MSVC/Visual Studio). + https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros + + - Since rc.exe is a legacy compiler, it truncates long symbol (> 30 chars) + and reports warning "RC4011: identifier truncated". + + - To eliminate the warning, we surround long preprocessor symbol with + "#if !defined(RC_INVOKED) ... #endif" block that means + "skip this block when rc.exe is trying to read it". +*/ +#if !defined(RC_INVOKED) /* https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros */ +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +LZ4LIB_API LZ4_stream_t* LZ4_createStream(void); +LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr); +#endif /* !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) */ +#endif + +/*! LZ4_resetStream_fast() : v1.9.0+ + * Use this to prepare an LZ4_stream_t for a new chain of dependent blocks + * (e.g., LZ4_compress_fast_continue()). + * + * An LZ4_stream_t must be initialized once before usage. + * This is automatically done when created by LZ4_createStream(). + * However, should the LZ4_stream_t be simply declared on stack (for example), + * it's necessary to initialize it first, using LZ4_initStream(). + * + * After init, start any new stream with LZ4_resetStream_fast(). + * A same LZ4_stream_t can be re-used multiple times consecutively + * and compress multiple streams, + * provided that it starts each new stream with LZ4_resetStream_fast(). + * + * LZ4_resetStream_fast() is much faster than LZ4_initStream(), + * but is not compatible with memory regions containing garbage data. + * + * Note: it's only useful to call LZ4_resetStream_fast() + * in the context of streaming compression. + * The *extState* functions perform their own resets. + * Invoking LZ4_resetStream_fast() before is redundant, and even counterproductive. + */ +LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr); + +/*! LZ4_loadDict() : + * Use this function to reference a static dictionary into LZ4_stream_t. + * The dictionary must remain available during compression. + * LZ4_loadDict() triggers a reset, so any previous data will be forgotten. + * The same dictionary will have to be loaded on decompression side for successful decoding. + * Dictionary are useful for better compression of small data (KB range). + * While LZ4 accept any input as dictionary, + * results are generally better when using Zstandard's Dictionary Builder. + * Loading a size of 0 is allowed, and is the same as reset. + * @return : loaded dictionary size, in bytes (necessarily <= 64 KB) + */ +LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); + +/*! LZ4_compress_fast_continue() : + * Compress 'src' content using data from previously compressed blocks, for better compression ratio. + * 'dst' buffer must be already allocated. + * If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster. + * + * @return : size of compressed block + * or 0 if there is an error (typically, cannot fit into 'dst'). + * + * Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block. + * Each block has precise boundaries. + * Each block must be decompressed separately, calling LZ4_decompress_*() with relevant metadata. + * It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together. + * + * Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory ! + * + * Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB. + * Make sure that buffers are separated, by at least one byte. + * This construction ensures that each block only depends on previous block. + * + * Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB. + * + * Note 5 : After an error, the stream status is undefined (invalid), it can only be reset or freed. + */ +LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_saveDict() : + * If last 64KB data cannot be guaranteed to remain available at its current memory location, + * save it into a safer place (char* safeBuffer). + * This is schematically equivalent to a memcpy() followed by LZ4_loadDict(), + * but is much faster, because LZ4_saveDict() doesn't need to rebuild tables. + * @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error. + */ +LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize); + + +/*-********************************************** +* Streaming Decompression Functions +* Bufferless synchronous API +************************************************/ +typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* tracking context */ + +/*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() : + * creation / destruction of streaming decompression tracking context. + * A tracking context can be re-used multiple times. + */ +#if !defined(RC_INVOKED) /* https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros */ +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void); +LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream); +#endif /* !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) */ +#endif + +/*! LZ4_setStreamDecode() : + * An LZ4_streamDecode_t context can be allocated once and re-used multiple times. + * Use this function to start decompression of a new stream of blocks. + * A dictionary can optionally be set. Use NULL or size 0 for a reset order. + * Dictionary is presumed stable : it must remain accessible and unmodified during next decompression. + * @return : 1 if OK, 0 if error + */ +LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize); + +/*! LZ4_decoderRingBufferSize() : v1.8.2+ + * Note : in a ring buffer scenario (optional), + * blocks are presumed decompressed next to each other + * up to the moment there is not enough remaining space for next block (remainingSize < maxBlockSize), + * at which stage it resumes from beginning of ring buffer. + * When setting such a ring buffer for streaming decompression, + * provides the minimum size of this ring buffer + * to be compatible with any source respecting maxBlockSize condition. + * @return : minimum ring buffer size, + * or 0 if there is an error (invalid maxBlockSize). + */ +LZ4LIB_API int LZ4_decoderRingBufferSize(int maxBlockSize); +#define LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize) (65536 + 14 + (maxBlockSize)) /* for static allocation; maxBlockSize presumed valid */ + +/*! LZ4_decompress_*_continue() : + * These decoding functions allow decompression of consecutive blocks in "streaming" mode. + * A block is an unsplittable entity, it must be presented entirely to a decompression function. + * Decompression functions only accepts one block at a time. + * The last 64KB of previously decoded data *must* remain available and unmodified at the memory position where they were decoded. + * If less than 64KB of data has been decoded, all the data must be present. + * + * Special : if decompression side sets a ring buffer, it must respect one of the following conditions : + * - Decompression buffer size is _at least_ LZ4_decoderRingBufferSize(maxBlockSize). + * maxBlockSize is the maximum size of any single block. It can have any value > 16 bytes. + * In which case, encoding and decoding buffers do not need to be synchronized. + * Actually, data can be produced by any source compliant with LZ4 format specification, and respecting maxBlockSize. + * - Synchronized mode : + * Decompression buffer size is _exactly_ the same as compression buffer size, + * and follows exactly same update rule (block boundaries at same positions), + * and decoding function is provided with exact decompressed size of each block (exception for last block of the stream), + * _then_ decoding & encoding ring buffer can have any size, including small ones ( < 64 KB). + * - Decompression buffer is larger than encoding buffer, by a minimum of maxBlockSize more bytes. + * In which case, encoding and decoding buffers do not need to be synchronized, + * and encoding ring buffer can have any size, including small ones ( < 64 KB). + * + * Whenever these conditions are not possible, + * save the last 64KB of decoded data into a safe buffer where it can't be modified during decompression, + * then indicate where this data is saved using LZ4_setStreamDecode(), before decompressing next block. +*/ +LZ4LIB_API int +LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, + const char* src, char* dst, + int srcSize, int dstCapacity); + + +/*! LZ4_decompress_*_usingDict() : + * These decoding functions work the same as + * a combination of LZ4_setStreamDecode() followed by LZ4_decompress_*_continue() + * They are stand-alone, and don't need an LZ4_streamDecode_t structure. + * Dictionary is presumed stable : it must remain accessible and unmodified during decompression. + * Performance tip : Decompression speed can be substantially increased + * when dst == dictStart + dictSize. + */ +LZ4LIB_API int +LZ4_decompress_safe_usingDict(const char* src, char* dst, + int srcSize, int dstCapacity, + const char* dictStart, int dictSize); + +LZ4LIB_API int +LZ4_decompress_safe_partial_usingDict(const char* src, char* dst, + int compressedSize, + int targetOutputSize, int maxOutputSize, + const char* dictStart, int dictSize); + +#endif /* LZ4_H_2983827168210 */ + + +/*^************************************* + * !!!!!! STATIC LINKING ONLY !!!!!! + ***************************************/ + +/*-**************************************************************************** + * Experimental section + * + * Symbols declared in this section must be considered unstable. Their + * signatures or semantics may change, or they may be removed altogether in the + * future. They are therefore only safe to depend on when the caller is + * statically linked against the library. + * + * To protect against unsafe usage, not only are the declarations guarded, + * the definitions are hidden by default + * when building LZ4 as a shared/dynamic library. + * + * In order to access these declarations, + * define LZ4_STATIC_LINKING_ONLY in your application + * before including LZ4's headers. + * + * In order to make their implementations accessible dynamically, you must + * define LZ4_PUBLISH_STATIC_FUNCTIONS when building the LZ4 library. + ******************************************************************************/ + +#ifdef LZ4_STATIC_LINKING_ONLY + +#ifndef LZ4_STATIC_3504398509 +#define LZ4_STATIC_3504398509 + +#ifdef LZ4_PUBLISH_STATIC_FUNCTIONS +#define LZ4LIB_STATIC_API LZ4LIB_API +#else +#define LZ4LIB_STATIC_API +#endif + + +/*! LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. + * It is only safe to call if the state buffer is known to be correctly initialized already + * (see above comment on LZ4_resetStream_fast() for a definition of "correctly initialized"). + * From a high level, the difference is that + * this function initializes the provided state with a call to something like LZ4_resetStream_fast() + * while LZ4_compress_fast_extState() starts with a call to LZ4_resetStream(). + */ +LZ4LIB_STATIC_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_attach_dictionary() : + * This is an experimental API that allows + * efficient use of a static dictionary many times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a + * working LZ4_stream_t, this function introduces a no-copy setup mechanism, + * in which the working stream references the dictionary stream in-place. + * + * Several assumptions are made about the state of the dictionary stream. + * Currently, only streams which have been prepared by LZ4_loadDict() should + * be expected to work. + * + * Alternatively, the provided dictionaryStream may be NULL, + * in which case any existing dictionary stream is unset. + * + * If a dictionary is provided, it replaces any pre-existing stream history. + * The dictionary contents are the only history that can be referenced and + * logically immediately precede the data compressed in the first subsequent + * compression call. + * + * The dictionary will only remain attached to the working stream through the + * first compression call, at the end of which it is cleared. The dictionary + * stream (and source buffer) must remain in-place / accessible / unchanged + * through the completion of the first compression call on the stream. + */ +LZ4LIB_STATIC_API void +LZ4_attach_dictionary(LZ4_stream_t* workingStream, + const LZ4_stream_t* dictionaryStream); + + +/*! In-place compression and decompression + * + * It's possible to have input and output sharing the same buffer, + * for highly constrained memory environments. + * In both cases, it requires input to lay at the end of the buffer, + * and decompression to start at beginning of the buffer. + * Buffer size must feature some margin, hence be larger than final size. + * + * |<------------------------buffer--------------------------------->| + * |<-----------compressed data--------->| + * |<-----------decompressed size------------------>| + * |<----margin---->| + * + * This technique is more useful for decompression, + * since decompressed size is typically larger, + * and margin is short. + * + * In-place decompression will work inside any buffer + * which size is >= LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize). + * This presumes that decompressedSize > compressedSize. + * Otherwise, it means compression actually expanded data, + * and it would be more efficient to store such data with a flag indicating it's not compressed. + * This can happen when data is not compressible (already compressed, or encrypted). + * + * For in-place compression, margin is larger, as it must be able to cope with both + * history preservation, requiring input data to remain unmodified up to LZ4_DISTANCE_MAX, + * and data expansion, which can happen when input is not compressible. + * As a consequence, buffer size requirements are much higher, + * and memory savings offered by in-place compression are more limited. + * + * There are ways to limit this cost for compression : + * - Reduce history size, by modifying LZ4_DISTANCE_MAX. + * Note that it is a compile-time constant, so all compressions will apply this limit. + * Lower values will reduce compression ratio, except when input_size < LZ4_DISTANCE_MAX, + * so it's a reasonable trick when inputs are known to be small. + * - Require the compressor to deliver a "maximum compressed size". + * This is the `dstCapacity` parameter in `LZ4_compress*()`. + * When this size is < LZ4_COMPRESSBOUND(inputSize), then compression can fail, + * in which case, the return code will be 0 (zero). + * The caller must be ready for these cases to happen, + * and typically design a backup scheme to send data uncompressed. + * The combination of both techniques can significantly reduce + * the amount of margin required for in-place compression. + * + * In-place compression can work in any buffer + * which size is >= (maxCompressedSize) + * with maxCompressedSize == LZ4_COMPRESSBOUND(srcSize) for guaranteed compression success. + * LZ4_COMPRESS_INPLACE_BUFFER_SIZE() depends on both maxCompressedSize and LZ4_DISTANCE_MAX, + * so it's possible to reduce memory requirements by playing with them. + */ + +#define LZ4_DECOMPRESS_INPLACE_MARGIN(compressedSize) (((compressedSize) >> 8) + 32) +#define LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize) ((decompressedSize) + LZ4_DECOMPRESS_INPLACE_MARGIN(decompressedSize)) /**< note: presumes that compressedSize < decompressedSize. note2: margin is overestimated a bit, since it could use compressedSize instead */ + +#ifndef LZ4_DISTANCE_MAX /* history window size; can be user-defined at compile time */ +# define LZ4_DISTANCE_MAX 65535 /* set to maximum value by default */ +#endif + +#define LZ4_COMPRESS_INPLACE_MARGIN (LZ4_DISTANCE_MAX + 32) /* LZ4_DISTANCE_MAX can be safely replaced by srcSize when it's smaller */ +#define LZ4_COMPRESS_INPLACE_BUFFER_SIZE(maxCompressedSize) ((maxCompressedSize) + LZ4_COMPRESS_INPLACE_MARGIN) /**< maxCompressedSize is generally LZ4_COMPRESSBOUND(inputSize), but can be set to any lower value, with the risk that compression can fail (return code 0(zero)) */ + +#endif /* LZ4_STATIC_3504398509 */ +#endif /* LZ4_STATIC_LINKING_ONLY */ + + + +#ifndef LZ4_H_98237428734687 +#define LZ4_H_98237428734687 + +/*-************************************************************ + * Private Definitions + ************************************************************** + * Do not use these definitions directly. + * They are only exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`. + * Accessing members will expose user code to API and/or ABI break in future versions of the library. + **************************************************************/ +#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) +#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE) +#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ + +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# include + typedef int8_t LZ4_i8; + typedef uint8_t LZ4_byte; + typedef uint16_t LZ4_u16; + typedef uint32_t LZ4_u32; +#else + typedef signed char LZ4_i8; + typedef unsigned char LZ4_byte; + typedef unsigned short LZ4_u16; + typedef unsigned int LZ4_u32; +#endif + +/*! LZ4_stream_t : + * Never ever use below internal definitions directly ! + * These definitions are not API/ABI safe, and may change in future versions. + * If you need static allocation, declare or allocate an LZ4_stream_t object. +**/ + +typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; +struct LZ4_stream_t_internal { + LZ4_u32 hashTable[LZ4_HASH_SIZE_U32]; + const LZ4_byte* dictionary; + const LZ4_stream_t_internal* dictCtx; + LZ4_u32 currentOffset; + LZ4_u32 tableType; + LZ4_u32 dictSize; + /* Implicit padding to ensure structure is aligned */ +}; + +#define LZ4_STREAM_MINSIZE ((1UL << LZ4_MEMORY_USAGE) + 32) /* static size, for inter-version compatibility */ +union LZ4_stream_u { + char minStateSize[LZ4_STREAM_MINSIZE]; + LZ4_stream_t_internal internal_donotuse; +}; /* previously typedef'd to LZ4_stream_t */ + + +/*! LZ4_initStream() : v1.9.0+ + * An LZ4_stream_t structure must be initialized at least once. + * This is automatically done when invoking LZ4_createStream(), + * but it's not when the structure is simply declared on stack (for example). + * + * Use LZ4_initStream() to properly initialize a newly declared LZ4_stream_t. + * It can also initialize any arbitrary buffer of sufficient size, + * and will @return a pointer of proper type upon initialization. + * + * Note : initialization fails if size and alignment conditions are not respected. + * In which case, the function will @return NULL. + * Note2: An LZ4_stream_t structure guarantees correct alignment and size. + * Note3: Before v1.9.0, use LZ4_resetStream() instead +**/ +LZ4LIB_API LZ4_stream_t* LZ4_initStream (void* buffer, size_t size); + + +/*! LZ4_streamDecode_t : + * Never ever use below internal definitions directly ! + * These definitions are not API/ABI safe, and may change in future versions. + * If you need static allocation, declare or allocate an LZ4_streamDecode_t object. +**/ +typedef struct { + const LZ4_byte* externalDict; + const LZ4_byte* prefixEnd; + size_t extDictSize; + size_t prefixSize; +} LZ4_streamDecode_t_internal; + +#define LZ4_STREAMDECODE_MINSIZE 32 +union LZ4_streamDecode_u { + char minStateSize[LZ4_STREAMDECODE_MINSIZE]; + LZ4_streamDecode_t_internal internal_donotuse; +} ; /* previously typedef'd to LZ4_streamDecode_t */ + + + +/*-************************************ +* Obsolete Functions +**************************************/ + +/*! Deprecation warnings + * + * Deprecated functions make the compiler generate a warning when invoked. + * This is meant to invite users to update their source code. + * Should deprecation warnings be a problem, it is generally possible to disable them, + * typically with -Wno-deprecated-declarations for gcc + * or _CRT_SECURE_NO_WARNINGS in Visual. + * + * Another method is to define LZ4_DISABLE_DEPRECATE_WARNINGS + * before including the header file. + */ +#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS +# define LZ4_DEPRECATED(message) /* disable deprecation warnings */ +#else +# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ +# define LZ4_DEPRECATED(message) [[deprecated(message)]] +# elif defined(_MSC_VER) +# define LZ4_DEPRECATED(message) __declspec(deprecated(message)) +# elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 45)) +# define LZ4_DEPRECATED(message) __attribute__((deprecated(message))) +# elif defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 31) +# define LZ4_DEPRECATED(message) __attribute__((deprecated)) +# else +# pragma message("WARNING: LZ4_DEPRECATED needs custom implementation for this compiler") +# define LZ4_DEPRECATED(message) /* disabled */ +# endif +#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */ + +/*! Obsolete compression functions (since v1.7.3) */ +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* src, char* dest, int srcSize); +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* src, char* dest, int srcSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize); + +/*! Obsolete decompression functions (since v1.8.0) */ +LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize); +LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize); + +/* Obsolete streaming functions (since v1.7.0) + * degraded functionality; do not use! + * + * In order to perform streaming compression, these functions depended on data + * that is no longer tracked in the state. They have been preserved as well as + * possible: using them will still produce a correct output. However, they don't + * actually retain any history between compression calls. The compression ratio + * achieved will therefore be no better than compressing each chunk + * independently. + */ +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void); +LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state); + +/*! Obsolete streaming decoding functions (since v1.7.0) */ +LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize); +LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize); + +/*! Obsolete LZ4_decompress_fast variants (since v1.9.0) : + * These functions used to be faster than LZ4_decompress_safe(), + * but this is no longer the case. They are now slower. + * This is because LZ4_decompress_fast() doesn't know the input size, + * and therefore must progress more cautiously into the input buffer to not read beyond the end of block. + * On top of that `LZ4_decompress_fast()` is not protected vs malformed or malicious inputs, making it a security liability. + * As a consequence, LZ4_decompress_fast() is strongly discouraged, and deprecated. + * + * The last remaining LZ4_decompress_fast() specificity is that + * it can decompress a block without knowing its compressed size. + * Such functionality can be achieved in a more secure manner + * by employing LZ4_decompress_safe_partial(). + * + * Parameters: + * originalSize : is the uncompressed size to regenerate. + * `dst` must be already allocated, its size must be >= 'originalSize' bytes. + * @return : number of bytes read from source buffer (== compressed size). + * The function expects to finish at block's end exactly. + * If the source stream is detected malformed, the function stops decoding and returns a negative result. + * note : LZ4_decompress_fast*() requires originalSize. Thanks to this information, it never writes past the output buffer. + * However, since it doesn't know its 'src' size, it may read an unknown amount of input, past input buffer bounds. + * Also, since match offsets are not validated, match reads from 'src' may underflow too. + * These issues never happen if input (compressed) data is correct. + * But they may happen if input data is invalid (error or intentional tampering). + * As a consequence, use these functions in trusted environments with trusted data **only**. + */ +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe() instead") +LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize); +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_continue() instead") +LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize); +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_usingDict() instead") +LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize); + +/*! LZ4_resetStream() : + * An LZ4_stream_t structure must be initialized at least once. + * This is done with LZ4_initStream(), or LZ4_resetStream(). + * Consider switching to LZ4_initStream(), + * invoking LZ4_resetStream() will trigger deprecation warnings in the future. + */ +LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr); + + +#endif /* LZ4_H_98237428734687 */ + + +#if defined (__cplusplus) +} +#endif diff --git a/contrib/tinyusdz/tinyusdz_repo/src/math-util.inc b/contrib/tinyusdz/tinyusdz_repo/src/math-util.inc new file mode 100644 index 000000000..b7815c8f3 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/math-util.inc @@ -0,0 +1,404 @@ +#pragma once + +#include "prim-types.hh" +#include "value-types.hh" + +namespace tinyusdz { + +namespace math { + +// TODO: Set TINYUSDZ_BIG_ENDIAN when compiling TinyUSDZ on Big Endian architecture(e.g. PowerPC, SPARC) +union IEEE754Float { + float f; + uint32_t ui; + int32_t i; + struct { +#if defined(TINYUSDZ_BIG_ENDIAN) + uint32_t sign : 1; + uint32_t exponent : 8; + uint32_t mantissa : 23; +#else + uint32_t mantissa : 23; + uint32_t exponent : 8; + uint32_t sign : 1; +#endif + } bits; +}; + +union IEEE754Double { + double f; + uint64_t ull; + int64_t ll; + struct { +#if defined(TINYUSD_BIG_ENDIAN) + uint64_t sign : 1; + uint64_t exponent : 11; + uint64_t mantissa : 52; +#else + uint64_t mantissa : 52; + uint64_t exponent : 11; + uint64_t sign : 1; +#endif + } bits; +}; + +template +struct constants; + +template <> +struct constants { + static constexpr float pi() { return float(3.141592653589793238462643383279502884e+00); } +}; + +template <> +struct constants { + static constexpr double pi() { return 3.141592653589793238462643383279502884e+00; } +}; + +inline float radian(float angle) { return 3.141592f * angle / 180.0f; } + +inline double radian(double angle) { return 3.141592653589793 * angle / 180.0; } + +inline float angle(float radian) { return radian * 180.0f / 3.141592f; } + +inline double angle(double radian) { + return radian * 180.0 / 3.141592653589793; +} + +inline bool is_close(const int16_t a, const int16_t b) { + return a == b; +} + +inline bool is_close(const uint16_t a, const uint16_t b) { + return a == b; +} + +inline bool is_close(const int32_t a, const int32_t b) { + return a == b; +} + +inline bool is_close(const uint32_t a, const uint32_t b) { + return a == b; +} + +// Simple subtraction is not robust. Use more robust fp diff compare. +// https://embeddeduse.com/2019/08/26/qt-compare-two-floats/ +// https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ +// TODO: ulp based comparison for more robust compare +inline bool is_close(float a, float b, const float eps = std::numeric_limits::epsilon()) { + float d = a - b; + if (std::fabs(d) <= eps) { + return true; + } + + return std::fabs(d) <= (eps * std::fmax(std::fabs(a), std::fabs(b))); +} + +inline bool is_close(double a, double b, const double eps = std::numeric_limits::epsilon()) { + double d = a - b; + if (std::fabs(d) <= eps) { + return true; + } + return std::fabs(d) <= (eps * std::fmax(std::fabs(a), std::fabs(b))); +} + +inline bool is_close(value::half a, value::half b, const float eps = std::numeric_limits::epsilon()) { + return is_close(value::half_to_float(a), value::half_to_float(b), eps); +} + +// Compare two floating point by ulps. +// Based on this blog post: https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ +// Use this function if you want to compare fp value with relative diffs. +// +// TODO(LTE): Consider nan, inf case. +// +inline bool almost_equals_by_ulps(float x, float y, uint32_t max_ulp_diffs) { + IEEE754Float flt_x; + IEEE754Float flt_y; + + flt_x.f = x; + flt_y.f = y; + + if (flt_x.bits.sign != flt_y.bits.sign) { + // Check if +0/-0 + if ((flt_x.bits.exponent == 0) && + (flt_y.bits.exponent == 0) && + (flt_x.bits.mantissa == 0) && + (flt_y.bits.mantissa == 0)) { + return true; + } + + return false; + } + + int32_t diff = int32_t(flt_x.ui) - int32_t(flt_y.ui); + + // abs() + diff = (diff < 0) ? -diff : diff; + + if (uint32_t(diff) <= max_ulp_diffs) { + return true; + } + + return false; +} + +inline bool almost_equals_by_ulps(double x, double y, uint64_t max_ulp_diffs) { + IEEE754Double flt_x; + IEEE754Double flt_y; + + flt_x.f = x; + flt_y.f = y; + + if (flt_x.bits.sign != flt_y.bits.sign) { + // Check if +0/-0 + if ((flt_x.bits.exponent == 0) && + (flt_y.bits.exponent == 0) && + (flt_x.bits.mantissa == 0) && + (flt_y.bits.mantissa == 0)) { + return true; + } + + return false; + } + + int64_t diff = int64_t(flt_x.ull) - int64_t(flt_y.ull); + + // abs() + diff = (diff < 0) ? -diff : diff; + + if (uint64_t(diff) <= max_ulp_diffs) { + return true; + } + + return false; +} + +// +// Computing sin(and cos) using angle * PI / 180.0 is problematic in some +// situation (notably 45*n, 90*n) +// https://stackoverflow.com/questions/6566512/value-of-sine-180-is-coming-out-as-1-22465e-16 +// +// Also, sin(radian(45.0)) != cos(radian(45.0)) => 1 bit difference +// +// sin_pi/cos_pi + work around for +/- (pi/4 * n) solves such issue. +// ported from boost + +// -------------------------------------------------------------------------- +// Copyright (c) 2007 John Maddock +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +template +inline T sin_pi_imp(T x) { + if (x < 0) return -sin_pi_imp(T(-x)); + // sin of pi*x: + bool invert{false}; + if (x < T(0.5)) { + + // [tinyusdz] sin(0.25*pi) still differs with cos(0.25*pi) + // (whereas boost is not though(they compute sin/cos in higher bits?) + if (is_close(x, T(0.25), T(0))) { + return std::cos(constants::pi() * x); + } + + return std::sin(constants::pi() * x); + } + if (x < 1) { + invert = true; + x = -x; + } else + invert = false; + + T rem = std::floor(x); + + // if(iconvert(rem) & 1) + // invert = !invert; + // + // iconvert = static_cast(truc(x)) + // TODO: Check resulting value is within int32 min/max. + // TODO: We may simply do int-type cast since rem should be small. + { + T r = std::trunc(rem); + int ival = static_cast(r); + if (ival & 1) { + invert = !invert; + } + } + + rem = x - rem; + if (rem > T(0.5)) rem = 1 - rem; + if (is_close(rem, T(0.5), T(0.0))) return static_cast(invert ? -1 : 1); + + // [tinyusdz] workaround for -(n * pi/4) and +(n * pi/4) so that it will give identical result with cosine. + if (is_close(rem, T(0.25), T(0))) { + rem = std::cos(constants::pi() * rem); + } else { + rem = std::sin(constants::pi() * rem); + } + + return invert ? T(-rem) : rem; +} + +template +inline T cos_pi_imp(T x) { + // cos of pi*x: + bool invert = false; + if(std::fabs(x) < 0.25) + return std::cos(constants::pi() * x); + + if(x < 0) + { + x = -x; + } + T rem = std::floor(x); + + //if(iconvert(rem, pol) & 1) + // invert = !invert; + { + T r = std::trunc(rem); + int ival = static_cast(r); + if (ival & 1) { + invert = !invert; + } + } + + rem = x - rem; + if(rem > T(0.5)) + { + rem = 1 - rem; + invert = !invert; + } + if(is_close(rem, T(0.5), T(0))) + return T(0); + + if(rem > T(0.25)) + { + rem = T(0.5) - rem; + rem = std::sin(constants::pi() * rem); + } + else + rem = std::cos(constants::pi() * rem); + return invert ? T(-rem) : rem; +} + + +// -------------------------------------------------------------------------- + +inline double sin_pi(double x) { + return sin_pi_imp(x); +} + +inline double cos_pi(double x) { + return cos_pi_imp(x); +} + +// +// is_close +// + +inline bool is_close(const value::half2 &a, const value::half2 &b, const float eps = std::numeric_limits::epsilon()) { + return is_close(a[0], b[0], eps) && is_close(a[1], b[1], eps); +} +inline bool is_close(const value::half3 &a, const value::half3 &b, const float eps = std::numeric_limits::epsilon()) { + return is_close(a[0], b[0], eps) && is_close(a[1], b[1], eps) && is_close(a[2], b[2], eps); +} +inline bool is_close(const value::half4 &a, const value::half4 &b, const float eps = std::numeric_limits::epsilon()) { + return is_close(a[0], b[0], eps) && is_close(a[1], b[1], eps) && is_close(a[2], b[2], eps) && is_close(a[3], b[3], eps); +} + +inline bool is_close(const value::float2 &a, const value::float2 &b, const float eps = std::numeric_limits::epsilon()) { + return is_close(a[0], b[0], eps) && is_close(a[1], b[1], eps); +} +inline bool is_close(const value::float3 &a, const value::float3 &b, const float eps = std::numeric_limits::epsilon()) { + return is_close(a[0], b[0], eps) && is_close(a[1], b[1], eps) && is_close(a[2], b[2], eps); +} +inline bool is_close(const value::float4 &a, const value::float4 &b, const float eps = std::numeric_limits::epsilon()) { + return is_close(a[0], b[0], eps) && is_close(a[1], b[1], eps) && is_close(a[2], b[2], eps) && is_close(a[3], b[3], eps); +} + +inline bool is_close(const value::double2 &a, const value::double2 &b, const double eps = std::numeric_limits::epsilon()) { + return is_close(a[0], b[0], eps) && is_close(a[1], b[1], eps); +} +inline bool is_close(const value::double3 &a, const value::double3 &b, const double eps = std::numeric_limits::epsilon()) { + return is_close(a[0], b[0], eps) && is_close(a[1], b[1], eps) && is_close(a[2], b[2], eps); +} +inline bool is_close(const value::double4 &a, const value::double4 &b, const double eps = std::numeric_limits::epsilon()) { + return is_close(a[0], b[0], eps) && is_close(a[1], b[1], eps) && is_close(a[2], b[2], eps) && is_close(a[3], b[3], eps); +} + +inline bool is_close(const bool a, const bool b) { + return a == b; +} + +inline bool is_close(const signed char a, const signed char b) { + return a == b; +} + +inline bool is_close(const value::char2 &a, const value::char2 &b) { + return (a[0] == b[0]) && (a[1] == b[1]); +} +inline bool is_close(const value::char4 &a, const value::char3 &b) { + return (a[0] == b[0]) && (a[1] == b[1]) && (a[2] == b[2]); +} +inline bool is_close(const value::char4 &a, const value::char4 &b) { + return (a[0] == b[0]) && (a[1] == b[1]) && (a[2] == b[2]) && (a[3] == b[3]); +} + +inline bool is_close(const uint8_t a, const uint8_t b) { + return a == b; +} + +inline bool is_close(const value::uchar2 &a, const value::uchar2 &b) { + return (a[0] == b[0]) && (a[1] == b[1]); +} +inline bool is_close(const value::uchar4 &a, const value::uchar3 &b) { + return (a[0] == b[0]) && (a[1] == b[1]) && (a[2] == b[2]); +} +inline bool is_close(const value::uchar4 &a, const value::uchar4 &b) { + return (a[0] == b[0]) && (a[1] == b[1]) && (a[2] == b[2]) && (a[3] == b[3]); +} + +inline bool is_close(const value::int2 &a, const value::int2 &b) { + return (a[0] == b[0]) && (a[1] == b[1]); +} +inline bool is_close(const value::int3 &a, const value::int3 &b) { + return (a[0] == b[0]) && (a[1] == b[1]) && (a[2] == b[2]); +} +inline bool is_close(const value::int4 &a, const value::int4 &b) { + return (a[0] == b[0]) && (a[1] == b[1]) && (a[2] == b[2]) && (a[3] == b[3]); +} +inline bool is_close(const value::uint2 &a, const value::uint2 &b) { + return (a[0] == b[0]) && (a[1] == b[1]); +} +inline bool is_close(const value::uint3 &a, const value::uint3 &b) { + return (a[0] == b[0]) && (a[1] == b[1]) && (a[2] == b[2]); +} +inline bool is_close(const value::uint4 &a, const value::uint4 &b) { + return (a[0] == b[0]) && (a[1] == b[1]) && (a[2] == b[2]) && (a[3] == b[3]); +} + +inline bool is_close(const value::short2 &a, const value::short2 &b) { + return (a[0] == b[0]) && (a[1] == b[1]); +} +inline bool is_close(const value::short3 &a, const value::short3 &b) { + return (a[0] == b[0]) && (a[1] == b[1]) && (a[2] == b[2]); +} +inline bool is_close(const value::short4 &a, const value::short4 &b) { + return (a[0] == b[0]) && (a[1] == b[1]) && (a[2] == b[2]) && (a[3] == b[3]); +} +inline bool is_close(const value::ushort2 &a, const value::ushort2 &b) { + return (a[0] == b[0]) && (a[1] == b[1]); +} +inline bool is_close(const value::ushort3 &a, const value::ushort4 &b) { + return (a[0] == b[0]) && (a[1] == b[1]) && (a[2] == b[2]); +} +inline bool is_close(const value::ushort4 &a, const value::ushort4 &b) { + return (a[0] == b[0]) && (a[1] == b[1]) && (a[2] == b[2]) && (a[3] == b[3]); +} + + +} // namespace math + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/nonstd/expected.hpp b/contrib/tinyusdz/tinyusdz_repo/src/nonstd/expected.hpp new file mode 100644 index 000000000..5b19bf0d7 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/nonstd/expected.hpp @@ -0,0 +1,3494 @@ +// This version targets C++11 and later. +// +// Copyright (C) 2016-2020 Martin Moene. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// expected lite is based on: +// A proposal to add a utility class to represent expected monad +// by Vicente J. Botet Escriba and Pierre Talbot. http:://wg21.link/p0323 + +#ifndef NONSTD_EXPECTED_LITE_HPP +#define NONSTD_EXPECTED_LITE_HPP + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#define expected_lite_MAJOR 0 +#define expected_lite_MINOR 6 +#define expected_lite_PATCH 3 + +#define expected_lite_VERSION expected_STRINGIFY(expected_lite_MAJOR) "." expected_STRINGIFY(expected_lite_MINOR) "." expected_STRINGIFY(expected_lite_PATCH) + +#define expected_STRINGIFY( x ) expected_STRINGIFY_( x ) +#define expected_STRINGIFY_( x ) #x + +// expected-lite configuration: + +#define nsel_EXPECTED_DEFAULT 0 +#define nsel_EXPECTED_NONSTD 1 +#define nsel_EXPECTED_STD 2 + +// tweak header support: + +#ifdef __has_include +# if __has_include() +# include +# endif +#define expected_HAVE_TWEAK_HEADER 1 +#else +#define expected_HAVE_TWEAK_HEADER 0 +//# pragma message("expected.hpp: Note: Tweak header not supported.") +#endif + +// expected selection and configuration: + +#if !defined( nsel_CONFIG_SELECT_EXPECTED ) +# define nsel_CONFIG_SELECT_EXPECTED ( nsel_HAVE_STD_EXPECTED ? nsel_EXPECTED_STD : nsel_EXPECTED_NONSTD ) +#endif + +// Proposal revisions: +// +// DXXXXR0: -- +// N4015 : -2 (2014-05-26) +// N4109 : -1 (2014-06-29) +// P0323R0: 0 (2016-05-28) +// P0323R1: 1 (2016-10-12) +// -------: +// P0323R2: 2 (2017-06-15) +// P0323R3: 3 (2017-10-15) +// P0323R4: 4 (2017-11-26) +// P0323R5: 5 (2018-02-08) +// P0323R6: 6 (2018-04-02) +// P0323R7: 7 (2018-06-22) * +// +// expected-lite uses 2 and higher + +#ifndef nsel_P0323R +# define nsel_P0323R 7 +#endif + +// Monadic operations proposal revisions: +// +// P2505R0: 0 (2021-12-12) +// P2505R1: 1 (2022-02-10) +// P2505R2: 2 (2022-04-15) +// P2505R3: 3 (2022-06-05) +// P2505R4: 4 (2022-06-15) +// P2505R5: 5 (2022-09-20) * +// +// expected-lite uses 5 + +#ifndef nsel_P2505R +# define nsel_P2505R 5 +#endif + +// Control presence of C++ exception handling (try and auto discover): + +#ifndef nsel_CONFIG_NO_EXCEPTIONS +# if defined(_MSC_VER) +# include // for _HAS_EXCEPTIONS +# endif +# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS) +# define nsel_CONFIG_NO_EXCEPTIONS 0 +# else +# define nsel_CONFIG_NO_EXCEPTIONS 1 +# endif +#endif + +// at default use SEH with MSVC for no C++ exceptions + +#ifndef nsel_CONFIG_NO_EXCEPTIONS_SEH +# define nsel_CONFIG_NO_EXCEPTIONS_SEH ( nsel_CONFIG_NO_EXCEPTIONS && _MSC_VER ) +#endif + +// C++ language version detection (C++23 is speculative): +// Note: VC14.0/1900 (VS2015) lacks too much from C++14. + +#ifndef nsel_CPLUSPLUS +# if defined(_MSVC_LANG ) && !defined(__clang__) +# define nsel_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) +# else +# define nsel_CPLUSPLUS __cplusplus +# endif +#endif + +#define nsel_CPP98_OR_GREATER ( nsel_CPLUSPLUS >= 199711L ) +#define nsel_CPP11_OR_GREATER ( nsel_CPLUSPLUS >= 201103L ) +#define nsel_CPP14_OR_GREATER ( nsel_CPLUSPLUS >= 201402L ) +#define nsel_CPP17_OR_GREATER ( nsel_CPLUSPLUS >= 201703L ) +#define nsel_CPP20_OR_GREATER ( nsel_CPLUSPLUS >= 202002L ) +#define nsel_CPP23_OR_GREATER ( nsel_CPLUSPLUS >= 202300L ) + +// Use C++23 std::expected if available and requested: + +#if nsel_CPP23_OR_GREATER && defined(__has_include ) +# if __has_include( ) +# define nsel_HAVE_STD_EXPECTED 1 +# else +# define nsel_HAVE_STD_EXPECTED 0 +# endif +#else +# define nsel_HAVE_STD_EXPECTED 0 +#endif + +#define nsel_USES_STD_EXPECTED ( (nsel_CONFIG_SELECT_EXPECTED == nsel_EXPECTED_STD) || ((nsel_CONFIG_SELECT_EXPECTED == nsel_EXPECTED_DEFAULT) && nsel_HAVE_STD_EXPECTED) ) + +// +// in_place: code duplicated in any-lite, expected-lite, expected-lite, value-ptr-lite, variant-lite: +// + +#ifndef nonstd_lite_HAVE_IN_PLACE_TYPES +#define nonstd_lite_HAVE_IN_PLACE_TYPES 1 + +// C++17 std::in_place in : + +#if nsel_CPP17_OR_GREATER + +#include + +namespace nonstd { + +using std::in_place; +using std::in_place_type; +using std::in_place_index; +using std::in_place_t; +using std::in_place_type_t; +using std::in_place_index_t; + +#define nonstd_lite_in_place_t( T) std::in_place_t +#define nonstd_lite_in_place_type_t( T) std::in_place_type_t +#define nonstd_lite_in_place_index_t(K) std::in_place_index_t + +#define nonstd_lite_in_place( T) std::in_place_t{} +#define nonstd_lite_in_place_type( T) std::in_place_type_t{} +#define nonstd_lite_in_place_index(K) std::in_place_index_t{} + +} // namespace nonstd + +#else // nsel_CPP17_OR_GREATER + +#include + +namespace nonstd { +namespace detail { + +template< class T > +struct in_place_type_tag {}; + +template< std::size_t K > +struct in_place_index_tag {}; + +} // namespace detail + +struct in_place_t {}; + +template< class T > +inline in_place_t in_place( detail::in_place_type_tag = detail::in_place_type_tag() ) +{ + return in_place_t(); +} + +template< std::size_t K > +inline in_place_t in_place( detail::in_place_index_tag = detail::in_place_index_tag() ) +{ + return in_place_t(); +} + +template< class T > +inline in_place_t in_place_type( detail::in_place_type_tag = detail::in_place_type_tag() ) +{ + return in_place_t(); +} + +template< std::size_t K > +inline in_place_t in_place_index( detail::in_place_index_tag = detail::in_place_index_tag() ) +{ + return in_place_t(); +} + +// mimic templated typedef: + +#define nonstd_lite_in_place_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag ) +#define nonstd_lite_in_place_type_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag ) +#define nonstd_lite_in_place_index_t(K) nonstd::in_place_t(&)( nonstd::detail::in_place_index_tag ) + +#define nonstd_lite_in_place( T) nonstd::in_place_type +#define nonstd_lite_in_place_type( T) nonstd::in_place_type +#define nonstd_lite_in_place_index(K) nonstd::in_place_index + +} // namespace nonstd + +#endif // nsel_CPP17_OR_GREATER +#endif // nonstd_lite_HAVE_IN_PLACE_TYPES + +// +// Using std::expected: +// + +#if nsel_USES_STD_EXPECTED + +#include + +namespace nonstd { + + using std::expected; +// ... +} + +#else // nsel_USES_STD_EXPECTED + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// additional includes: + +#if nsel_CONFIG_NO_EXCEPTIONS +# if nsel_CONFIG_NO_EXCEPTIONS_SEH +# include // for ExceptionCodes +# else +// already included: +# endif +#else +# include +#endif + +// C++ feature usage: + +#if nsel_CPP11_OR_GREATER +# define nsel_constexpr constexpr +#else +# define nsel_constexpr /*constexpr*/ +#endif + +#if nsel_CPP14_OR_GREATER +# define nsel_constexpr14 constexpr +#else +# define nsel_constexpr14 /*constexpr*/ +#endif + +#if nsel_CPP17_OR_GREATER +# define nsel_inline17 inline +#else +# define nsel_inline17 /*inline*/ +#endif + +// Compiler versions: +// +// MSVC++ 6.0 _MSC_VER == 1200 nsel_COMPILER_MSVC_VERSION == 60 (Visual Studio 6.0) +// MSVC++ 7.0 _MSC_VER == 1300 nsel_COMPILER_MSVC_VERSION == 70 (Visual Studio .NET 2002) +// MSVC++ 7.1 _MSC_VER == 1310 nsel_COMPILER_MSVC_VERSION == 71 (Visual Studio .NET 2003) +// MSVC++ 8.0 _MSC_VER == 1400 nsel_COMPILER_MSVC_VERSION == 80 (Visual Studio 2005) +// MSVC++ 9.0 _MSC_VER == 1500 nsel_COMPILER_MSVC_VERSION == 90 (Visual Studio 2008) +// MSVC++ 10.0 _MSC_VER == 1600 nsel_COMPILER_MSVC_VERSION == 100 (Visual Studio 2010) +// MSVC++ 11.0 _MSC_VER == 1700 nsel_COMPILER_MSVC_VERSION == 110 (Visual Studio 2012) +// MSVC++ 12.0 _MSC_VER == 1800 nsel_COMPILER_MSVC_VERSION == 120 (Visual Studio 2013) +// MSVC++ 14.0 _MSC_VER == 1900 nsel_COMPILER_MSVC_VERSION == 140 (Visual Studio 2015) +// MSVC++ 14.1 _MSC_VER >= 1910 nsel_COMPILER_MSVC_VERSION == 141 (Visual Studio 2017) +// MSVC++ 14.2 _MSC_VER >= 1920 nsel_COMPILER_MSVC_VERSION == 142 (Visual Studio 2019) + +#if defined(_MSC_VER) && !defined(__clang__) +# define nsel_COMPILER_MSVC_VER (_MSC_VER ) +# define nsel_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900)) ) +#else +# define nsel_COMPILER_MSVC_VER 0 +# define nsel_COMPILER_MSVC_VERSION 0 +#endif + +#define nsel_COMPILER_VERSION( major, minor, patch ) ( 10 * ( 10 * (major) + (minor) ) + (patch) ) + +#if defined(__clang__) +# define nsel_COMPILER_CLANG_VERSION nsel_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) +#else +# define nsel_COMPILER_CLANG_VERSION 0 +#endif + +#if defined(__GNUC__) && !defined(__clang__) +# define nsel_COMPILER_GNUC_VERSION nsel_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#else +# define nsel_COMPILER_GNUC_VERSION 0 +#endif + +// half-open range [lo..hi): +//#define nsel_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) ) + +// Method enabling + +#define nsel_REQUIRES_0(...) \ + template< bool B = (__VA_ARGS__), typename std::enable_if::type = 0 > + +#define nsel_REQUIRES_T(...) \ + , typename std::enable_if< (__VA_ARGS__), int >::type = 0 + +#define nsel_REQUIRES_R(R, ...) \ + typename std::enable_if< (__VA_ARGS__), R>::type + +#define nsel_REQUIRES_A(...) \ + , typename std::enable_if< (__VA_ARGS__), void*>::type = nullptr + +// Presence of language and library features: + +#ifdef _HAS_CPP0X +# define nsel_HAS_CPP0X _HAS_CPP0X +#else +# define nsel_HAS_CPP0X 0 +#endif + +//#define nsel_CPP11_140 (nsel_CPP11_OR_GREATER || nsel_COMPILER_MSVC_VER >= 1900) + +// Clang, GNUC, MSVC warning suppression macros: + +#ifdef __clang__ +# pragma clang diagnostic push +#elif defined __GNUC__ +# pragma GCC diagnostic push +#endif // __clang__ + +#if nsel_COMPILER_MSVC_VERSION >= 140 +# pragma warning( push ) +# define nsel_DISABLE_MSVC_WARNINGS(codes) __pragma( warning(disable: codes) ) +#else +# define nsel_DISABLE_MSVC_WARNINGS(codes) +#endif + +#ifdef __clang__ +# define nsel_RESTORE_WARNINGS() _Pragma("clang diagnostic pop") +#elif defined __GNUC__ +# define nsel_RESTORE_WARNINGS() _Pragma("GCC diagnostic pop") +#elif nsel_COMPILER_MSVC_VERSION >= 140 +# define nsel_RESTORE_WARNINGS() __pragma( warning( pop ) ) +#else +# define nsel_RESTORE_WARNINGS() +#endif + +// Suppress the following MSVC (GSL) warnings: +// - C26409: Avoid calling new and delete explicitly, use std::make_unique instead (r.11) + +nsel_DISABLE_MSVC_WARNINGS( 26409 ) + +// +// expected: +// + +namespace nonstd { namespace expected_lite { + +// type traits C++17: + +namespace std17 { + +#if nsel_CPP17_OR_GREATER + +using std::conjunction; +using std::is_swappable; +using std::is_nothrow_swappable; + +#else // nsel_CPP17_OR_GREATER + +namespace detail { + +using std::swap; + +struct is_swappable +{ + template< typename T, typename = decltype( swap( std::declval(), std::declval() ) ) > + static std::true_type test( int /* unused */); + + template< typename > + static std::false_type test(...); +}; + +struct is_nothrow_swappable +{ + // wrap noexcept(expr) in separate function as work-around for VC140 (VS2015): + + template< typename T > + static constexpr bool satisfies() + { + return noexcept( swap( std::declval(), std::declval() ) ); + } + + template< typename T > + static auto test( int ) -> std::integral_constant()>{} + + template< typename > + static auto test(...) -> std::false_type; +}; +} // namespace detail + +// is [nothrow] swappable: + +template< typename T > +struct is_swappable : decltype( detail::is_swappable::test(0) ){}; + +template< typename T > +struct is_nothrow_swappable : decltype( detail::is_nothrow_swappable::test(0) ){}; + +// conjunction: + +template< typename... > struct conjunction : std::true_type{}; +template< typename B1 > struct conjunction : B1{}; + +template< typename B1, typename... Bn > +struct conjunction : std::conditional, B1>::type{}; + +#endif // nsel_CPP17_OR_GREATER + +} // namespace std17 + +// type traits C++20: + +namespace std20 { + +#if defined(__cpp_lib_remove_cvref) + +using std::remove_cvref; + +#else + +template< typename T > +struct remove_cvref +{ + typedef typename std::remove_cv< typename std::remove_reference::type >::type type; +}; + +#endif + +} // namespace std20 + +// forward declaration: + +template< typename T, typename E > +class expected; + +namespace detail { + +#if nsel_P2505R >= 3 +template< typename T > +struct is_expected : std::false_type {}; + +template< typename T, typename E > +struct is_expected< expected< T, E > > : std::true_type {}; +#endif // nsel_P2505R >= 3 + +/// discriminated union to hold value or 'error'. + +template< typename T, typename E > +class storage_t_noncopy_nonmove_impl +{ + template< typename, typename > friend class nonstd::expected_lite::expected; + +public: + using value_type = T; + using error_type = E; + + // no-op construction + storage_t_noncopy_nonmove_impl() {} + ~storage_t_noncopy_nonmove_impl() {} + + explicit storage_t_noncopy_nonmove_impl( bool has_value ) + : m_has_value( has_value ) + {} + + void construct_value() + { + new( &m_value ) value_type(); + } + + // void construct_value( value_type const & e ) + // { + // new( &m_value ) value_type( e ); + // } + + // void construct_value( value_type && e ) + // { + // new( &m_value ) value_type( std::move( e ) ); + // } + + template< class... Args > + void emplace_value( Args&&... args ) + { + new( &m_value ) value_type( std::forward(args)...); + } + + template< class U, class... Args > + void emplace_value( std::initializer_list il, Args&&... args ) + { + new( &m_value ) value_type( il, std::forward(args)... ); + } + + void destruct_value() + { + m_value.~value_type(); + } + + // void construct_error( error_type const & e ) + // { + // // new( &m_error ) error_type( e ); + // } + + // void construct_error( error_type && e ) + // { + // // new( &m_error ) error_type( std::move( e ) ); + // } + + template< class... Args > + void emplace_error( Args&&... args ) + { + new( &m_error ) error_type( std::forward(args)...); + } + + template< class U, class... Args > + void emplace_error( std::initializer_list il, Args&&... args ) + { + new( &m_error ) error_type( il, std::forward(args)... ); + } + + void destruct_error() + { + m_error.~error_type(); + } + + constexpr value_type const & value() const & + { + return m_value; + } + + value_type & value() & + { + return m_value; + } + + constexpr value_type const && value() const && + { + return std::move( m_value ); + } + + nsel_constexpr14 value_type && value() && + { + return std::move( m_value ); + } + + value_type const * value_ptr() const + { + return &m_value; + } + + value_type * value_ptr() + { + return &m_value; + } + + error_type const & error() const & + { + return m_error; + } + + error_type & error() & + { + return m_error; + } + + constexpr error_type const && error() const && + { + return std::move( m_error ); + } + + nsel_constexpr14 error_type && error() && + { + return std::move( m_error ); + } + + bool has_value() const + { + return m_has_value; + } + + void set_has_value( bool v ) + { + m_has_value = v; + } + +private: + union + { + value_type m_value; + error_type m_error; + }; + + bool m_has_value = false; +}; + +template< typename T, typename E > +class storage_t_impl +{ + template< typename, typename > friend class nonstd::expected_lite::expected; + +public: + using value_type = T; + using error_type = E; + + // no-op construction + storage_t_impl() {} + ~storage_t_impl() {} + + explicit storage_t_impl( bool has_value ) + : m_has_value( has_value ) + {} + + void construct_value() + { + new( &m_value ) value_type(); + } + + void construct_value( value_type const & e ) + { + new( &m_value ) value_type( e ); + } + + void construct_value( value_type && e ) + { + new( &m_value ) value_type( std::move( e ) ); + } + + template< class... Args > + void emplace_value( Args&&... args ) + { + new( &m_value ) value_type( std::forward(args)...); + } + + template< class U, class... Args > + void emplace_value( std::initializer_list il, Args&&... args ) + { + new( &m_value ) value_type( il, std::forward(args)... ); + } + + void destruct_value() + { + m_value.~value_type(); + } + + void construct_error( error_type const & e ) + { + new( &m_error ) error_type( e ); + } + + void construct_error( error_type && e ) + { + new( &m_error ) error_type( std::move( e ) ); + } + + template< class... Args > + void emplace_error( Args&&... args ) + { + new( &m_error ) error_type( std::forward(args)...); + } + + template< class U, class... Args > + void emplace_error( std::initializer_list il, Args&&... args ) + { + new( &m_error ) error_type( il, std::forward(args)... ); + } + + void destruct_error() + { + m_error.~error_type(); + } + + constexpr value_type const & value() const & + { + return m_value; + } + + value_type & value() & + { + return m_value; + } + + constexpr value_type const && value() const && + { + return std::move( m_value ); + } + + nsel_constexpr14 value_type && value() && + { + return std::move( m_value ); + } + + value_type const * value_ptr() const + { + return &m_value; + } + + value_type * value_ptr() + { + return &m_value; + } + + error_type const & error() const & + { + return m_error; + } + + error_type & error() & + { + return m_error; + } + + constexpr error_type const && error() const && + { + return std::move( m_error ); + } + + nsel_constexpr14 error_type && error() && + { + return std::move( m_error ); + } + + bool has_value() const + { + return m_has_value; + } + + void set_has_value( bool v ) + { + m_has_value = v; + } + +private: + union + { + value_type m_value; + error_type m_error; + }; + + bool m_has_value = false; +}; + +/// discriminated union to hold only 'error'. + +template< typename E > +struct storage_t_impl +{ + template< typename, typename > friend class nonstd::expected_lite::expected; + +public: + using value_type = void; + using error_type = E; + + // no-op construction + storage_t_impl() {} + ~storage_t_impl() {} + + explicit storage_t_impl( bool has_value ) + : m_has_value( has_value ) + {} + + void construct_error( error_type const & e ) + { + new( &m_error ) error_type( e ); + } + + void construct_error( error_type && e ) + { + new( &m_error ) error_type( std::move( e ) ); + } + + template< class... Args > + void emplace_error( Args&&... args ) + { + new( &m_error ) error_type( std::forward(args)...); + } + + template< class U, class... Args > + void emplace_error( std::initializer_list il, Args&&... args ) + { + new( &m_error ) error_type( il, std::forward(args)... ); + } + + void destruct_error() + { + m_error.~error_type(); + } + + error_type const & error() const & + { + return m_error; + } + + error_type & error() & + { + return m_error; + } + + constexpr error_type const && error() const && + { + return std::move( m_error ); + } + + nsel_constexpr14 error_type && error() && + { + return std::move( m_error ); + } + + bool has_value() const + { + return m_has_value; + } + + void set_has_value( bool v ) + { + m_has_value = v; + } + +private: + union + { + char m_dummy; + error_type m_error; + }; + + bool m_has_value = false; +}; + +template< typename T, typename E, bool isConstructable, bool isMoveable > +class storage_t +{ +public: +}; + +template< typename T, typename E > +class storage_t : public storage_t_noncopy_nonmove_impl +{ +public: + storage_t() = default; + ~storage_t() = default; + + explicit storage_t( bool has_value ) + : storage_t_noncopy_nonmove_impl( has_value ) + {} + + storage_t( storage_t const & other ) = delete; + storage_t( storage_t && other ) = delete; + +}; + +template< typename T, typename E > +class storage_t : public storage_t_impl +{ +public: + storage_t() = default; + ~storage_t() = default; + + explicit storage_t( bool has_value ) + : storage_t_impl( has_value ) + {} + + storage_t( storage_t const & other ) + : storage_t_impl( other.has_value() ) + { + if ( this->has_value() ) this->construct_value( other.value() ); + else this->construct_error( other.error() ); + } + + storage_t(storage_t && other ) + : storage_t_impl( other.has_value() ) + { + if ( this->has_value() ) this->construct_value( std::move( other.value() ) ); + else this->construct_error( std::move( other.error() ) ); + } +}; + +template< typename E > +class storage_t : public storage_t_impl +{ +public: + storage_t() = default; + ~storage_t() = default; + + explicit storage_t( bool has_value ) + : storage_t_impl( has_value ) + {} + + storage_t( storage_t const & other ) + : storage_t_impl( other.has_value() ) + { + if ( this->has_value() ) ; + else this->construct_error( other.error() ); + } + + storage_t(storage_t && other ) + : storage_t_impl( other.has_value() ) + { + if ( this->has_value() ) ; + else this->construct_error( std::move( other.error() ) ); + } +}; + +template< typename T, typename E > +class storage_t : public storage_t_impl +{ +public: + storage_t() = default; + ~storage_t() = default; + + explicit storage_t( bool has_value ) + : storage_t_impl( has_value ) + {} + + storage_t( storage_t const & other ) + : storage_t_impl(other.has_value()) + { + if ( this->has_value() ) this->construct_value( other.value() ); + else this->construct_error( other.error() ); + } + + storage_t( storage_t && other ) = delete; +}; + +template< typename E > +class storage_t : public storage_t_impl +{ +public: + storage_t() = default; + ~storage_t() = default; + + explicit storage_t( bool has_value ) + : storage_t_impl( has_value ) + {} + + storage_t( storage_t const & other ) + : storage_t_impl(other.has_value()) + { + if ( this->has_value() ) ; + else this->construct_error( other.error() ); + } + + storage_t( storage_t && other ) = delete; +}; + +template< typename T, typename E > +class storage_t : public storage_t_impl +{ +public: + storage_t() = default; + ~storage_t() = default; + + explicit storage_t( bool has_value ) + : storage_t_impl( has_value ) + {} + + storage_t( storage_t const & other ) = delete; + + storage_t( storage_t && other ) + : storage_t_impl( other.has_value() ) + { + if ( this->has_value() ) this->construct_value( std::move( other.value() ) ); + else this->construct_error( std::move( other.error() ) ); + } +}; + +template< typename E > +class storage_t : public storage_t_impl +{ +public: + storage_t() = default; + ~storage_t() = default; + + explicit storage_t( bool has_value ) + : storage_t_impl( has_value ) + {} + + storage_t( storage_t const & other ) = delete; + + storage_t( storage_t && other ) + : storage_t_impl( other.has_value() ) + { + if ( this->has_value() ) ; + else this->construct_error( std::move( other.error() ) ); + } +}; + +#if nsel_P2505R >= 3 +// C++11 invoke implementation +template< typename > +struct is_reference_wrapper : std::false_type {}; +template< typename T > +struct is_reference_wrapper< std::reference_wrapper< T > > : std::true_type {}; + +template< typename FnT, typename ClassT, typename ObjectT, typename... Args + nsel_REQUIRES_T( + std::is_function::value + && ( std::is_same< ClassT, typename std20::remove_cvref< ObjectT >::type >::value + || std::is_base_of< ClassT, typename std20::remove_cvref< ObjectT >::type >::value ) + ) +> +nsel_constexpr auto invoke_member_function_impl( FnT ClassT::* memfnptr, ObjectT && obj, Args && ... args ) + noexcept( noexcept( (std::forward< ObjectT >( obj ).*memfnptr)( std::forward< Args >( args )... ) ) ) + -> decltype( (std::forward< ObjectT >( obj ).*memfnptr)( std::forward< Args >( args )...) ) +{ + return (std::forward< ObjectT >( obj ).*memfnptr)( std::forward< Args >( args )... ); +} + +template< typename FnT, typename ClassT, typename ObjectT, typename... Args + nsel_REQUIRES_T( + std::is_function::value + && is_reference_wrapper< typename std20::remove_cvref< ObjectT >::type >::value + ) +> +nsel_constexpr auto invoke_member_function_impl( FnT ClassT::* memfnptr, ObjectT && obj, Args && ... args ) + noexcept( noexcept( (obj.get().*memfnptr)( std::forward< Args >( args ) ... ) ) ) + -> decltype( (obj.get().*memfnptr)( std::forward< Args >( args ) ... ) ) +{ + return (obj.get().*memfnptr)( std::forward< Args >( args ) ... ); +} + +template< typename FnT, typename ClassT, typename ObjectT, typename... Args + nsel_REQUIRES_T( + std::is_function::value + && !std::is_same< ClassT, typename std20::remove_cvref< ObjectT >::type >::value + && !std::is_base_of< ClassT, typename std20::remove_cvref< ObjectT >::type >::value + && !is_reference_wrapper< typename std20::remove_cvref< ObjectT >::type >::value + ) +> +nsel_constexpr auto invoke_member_function_impl( FnT ClassT::* memfnptr, ObjectT && obj, Args && ... args ) + noexcept( noexcept( ((*std::forward< ObjectT >( obj )).*memfnptr)( std::forward< Args >( args ) ... ) ) ) + -> decltype( ((*std::forward< ObjectT >( obj )).*memfnptr)( std::forward< Args >( args ) ... ) ) +{ + return ((*std::forward(obj)).*memfnptr)( std::forward< Args >( args ) ... ); +} + +template< typename MemberT, typename ClassT, typename ObjectT + nsel_REQUIRES_T( + std::is_same< ClassT, typename std20::remove_cvref< ObjectT >::type >::value + || std::is_base_of< ClassT, typename std20::remove_cvref< ObjectT >::type >::value + ) +> +nsel_constexpr auto invoke_member_object_impl( MemberT ClassT::* memobjptr, ObjectT && obj ) + noexcept( noexcept( std::forward< ObjectT >( obj ).*memobjptr ) ) + -> decltype( std::forward< ObjectT >( obj ).*memobjptr ) +{ + return std::forward< ObjectT >( obj ).*memobjptr; +} + +template< typename MemberT, typename ClassT, typename ObjectT + nsel_REQUIRES_T( + is_reference_wrapper< typename std20::remove_cvref< ObjectT >::type >::value + ) +> +nsel_constexpr auto invoke_member_object_impl( MemberT ClassT::* memobjptr, ObjectT && obj ) + noexcept( noexcept( obj.get().*memobjptr ) ) + -> decltype( obj.get().*memobjptr ) +{ + return obj.get().*memobjptr; +} + +template< typename MemberT, typename ClassT, typename ObjectT + nsel_REQUIRES_T( + !std::is_same< ClassT, typename std20::remove_cvref< ObjectT >::type >::value + && !std::is_base_of< ClassT, typename std20::remove_cvref< ObjectT >::type >::value + && !is_reference_wrapper< typename std20::remove_cvref< ObjectT >::type >::value + ) +> +nsel_constexpr auto invoke_member_object_impl( MemberT ClassT::* memobjptr, ObjectT && obj ) + noexcept( noexcept( (*std::forward< ObjectT >( obj )).*memobjptr ) ) + -> decltype( (*std::forward< ObjectT >( obj )).*memobjptr ) +{ + return (*std::forward< ObjectT >( obj )).*memobjptr; +} + +template< typename F, typename... Args + nsel_REQUIRES_T( + std::is_member_function_pointer< typename std20::remove_cvref< F >::type >::value + ) +> +nsel_constexpr auto invoke( F && f, Args && ... args ) + noexcept( noexcept( invoke_member_function_impl( std::forward< F >( f ), std::forward< Args >( args ) ... ) ) ) + -> decltype( invoke_member_function_impl( std::forward< F >( f ), std::forward< Args >( args ) ... ) ) +{ + return invoke_member_function_impl( std::forward< F >( f ), std::forward< Args >( args ) ... ); +} + +template< typename F, typename... Args + nsel_REQUIRES_T( + std::is_member_object_pointer< typename std20::remove_cvref< F >::type >::value + ) +> +nsel_constexpr auto invoke( F && f, Args && ... args ) + noexcept( noexcept( invoke_member_object_impl( std::forward< F >( f ), std::forward< Args >( args ) ... ) ) ) + -> decltype( invoke_member_object_impl( std::forward< F >( f ), std::forward< Args >( args ) ... ) ) +{ + return invoke_member_object_impl( std::forward< F >( f ), std::forward< Args >( args ) ... ); +} + +template< typename F, typename... Args + nsel_REQUIRES_T( + !std::is_member_function_pointer< typename std20::remove_cvref< F >::type >::value + && !std::is_member_object_pointer< typename std20::remove_cvref< F >::type >::value + ) +> +nsel_constexpr auto invoke( F && f, Args && ... args ) + noexcept( noexcept( std::forward< F >( f )( std::forward< Args >( args ) ... ) ) ) + -> decltype( std::forward< F >( f )( std::forward< Args >( args ) ... ) ) +{ + return std::forward< F >( f )( std::forward< Args >( args ) ... ); +} + +template< typename F, typename ... Args > +using invoke_result_nocvref_t = typename std20::remove_cvref< decltype( invoke( std::declval< F >(), std::declval< Args >()... ) ) >::type; + +#if nsel_P2505R >= 5 +template< typename F, typename ... Args > +using transform_invoke_result_t = typename std::remove_cv< decltype( invoke( std::declval< F >(), std::declval< Args >()... ) ) >::type; +#else +template< typename F, typename ... Args > +using transform_invoke_result_t = invoke_result_nocvref_t +#endif // nsel_P2505R >= 5 + +template< typename T > +struct valid_expected_value_type : std::integral_constant< bool, std::is_destructible< T >::value && !std::is_reference< T >::value && !std::is_array< T >::value > {}; + +#endif // nsel_P2505R >= 3 +} // namespace detail + +/// x.x.5 Unexpected object type; unexpected_type; C++17 and later can also use aliased type unexpected. + +#if nsel_P0323R <= 2 +template< typename E = std::exception_ptr > +class unexpected_type +#else +template< typename E > +class unexpected_type +#endif // nsel_P0323R +{ +public: + using error_type = E; + + // x.x.5.2.1 Constructors + +// unexpected_type() = delete; + + constexpr unexpected_type( unexpected_type const & ) = default; + constexpr unexpected_type( unexpected_type && ) = default; + + template< typename... Args + nsel_REQUIRES_T( + std::is_constructible::value + ) + > + constexpr explicit unexpected_type( nonstd_lite_in_place_t(E), Args &&... args ) + : m_error( std::forward( args )...) + {} + + template< typename U, typename... Args + nsel_REQUIRES_T( + std::is_constructible, Args&&...>::value + ) + > + constexpr explicit unexpected_type( nonstd_lite_in_place_t(E), std::initializer_list il, Args &&... args ) + : m_error( il, std::forward( args )...) + {} + + template< typename E2 + nsel_REQUIRES_T( + std::is_constructible::value + && !std::is_same< typename std20::remove_cvref::type, nonstd_lite_in_place_t(E2) >::value + && !std::is_same< typename std20::remove_cvref::type, unexpected_type >::value + ) + > + constexpr explicit unexpected_type( E2 && error ) + : m_error( std::forward( error ) ) + {} + + template< typename E2 + nsel_REQUIRES_T( + std::is_constructible< E, E2>::value + && !std::is_constructible & >::value + && !std::is_constructible >::value + && !std::is_constructible const & >::value + && !std::is_constructible const >::value + && !std::is_convertible< unexpected_type &, E>::value + && !std::is_convertible< unexpected_type , E>::value + && !std::is_convertible< unexpected_type const &, E>::value + && !std::is_convertible< unexpected_type const , E>::value + && !std::is_convertible< E2 const &, E>::value /*=> explicit */ + ) + > + constexpr explicit unexpected_type( unexpected_type const & error ) + : m_error( E{ error.value() } ) + {} + + template< typename E2 + nsel_REQUIRES_T( + std::is_constructible< E, E2>::value + && !std::is_constructible & >::value + && !std::is_constructible >::value + && !std::is_constructible const & >::value + && !std::is_constructible const >::value + && !std::is_convertible< unexpected_type &, E>::value + && !std::is_convertible< unexpected_type , E>::value + && !std::is_convertible< unexpected_type const &, E>::value + && !std::is_convertible< unexpected_type const , E>::value + && std::is_convertible< E2 const &, E>::value /*=> explicit */ + ) + > + constexpr /*non-explicit*/ unexpected_type( unexpected_type const & error ) + : m_error( error.value() ) + {} + + template< typename E2 + nsel_REQUIRES_T( + std::is_constructible< E, E2>::value + && !std::is_constructible & >::value + && !std::is_constructible >::value + && !std::is_constructible const & >::value + && !std::is_constructible const >::value + && !std::is_convertible< unexpected_type &, E>::value + && !std::is_convertible< unexpected_type , E>::value + && !std::is_convertible< unexpected_type const &, E>::value + && !std::is_convertible< unexpected_type const , E>::value + && !std::is_convertible< E2 const &, E>::value /*=> explicit */ + ) + > + constexpr explicit unexpected_type( unexpected_type && error ) + : m_error( E{ std::move( error.value() ) } ) + {} + + template< typename E2 + nsel_REQUIRES_T( + std::is_constructible< E, E2>::value + && !std::is_constructible & >::value + && !std::is_constructible >::value + && !std::is_constructible const & >::value + && !std::is_constructible const >::value + && !std::is_convertible< unexpected_type &, E>::value + && !std::is_convertible< unexpected_type , E>::value + && !std::is_convertible< unexpected_type const &, E>::value + && !std::is_convertible< unexpected_type const , E>::value + && std::is_convertible< E2 const &, E>::value /*=> non-explicit */ + ) + > + constexpr /*non-explicit*/ unexpected_type( unexpected_type && error ) + : m_error( std::move( error.value() ) ) + {} + + // x.x.5.2.2 Assignment + + nsel_constexpr14 unexpected_type& operator=( unexpected_type const & ) = default; + nsel_constexpr14 unexpected_type& operator=( unexpected_type && ) = default; + + template< typename E2 = E > + nsel_constexpr14 unexpected_type & operator=( unexpected_type const & other ) + { + unexpected_type{ other.value() }.swap( *this ); + return *this; + } + + template< typename E2 = E > + nsel_constexpr14 unexpected_type & operator=( unexpected_type && other ) + { + unexpected_type{ std::move( other.value() ) }.swap( *this ); + return *this; + } + + // x.x.5.2.3 Observers + + nsel_constexpr14 E & value() & noexcept + { + return m_error; + } + + constexpr E const & value() const & noexcept + { + return m_error; + } + +#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490 + + nsel_constexpr14 E && value() && noexcept + { + return std::move( m_error ); + } + + constexpr E const && value() const && noexcept + { + return std::move( m_error ); + } + +#endif + + // x.x.5.2.4 Swap + + template< typename U=E > + nsel_REQUIRES_R( void, + std17::is_swappable::value + ) + swap( unexpected_type & other ) noexcept ( + std17::is_nothrow_swappable::value + ) + { + using std::swap; + swap( m_error, other.m_error ); + } + + // TODO: ??? unexpected_type: in-class friend operator==, != + +private: + error_type m_error; +}; + +#if nsel_CPP17_OR_GREATER + +/// template deduction guide: + +template< typename E > +unexpected_type( E ) -> unexpected_type< E >; + +#endif + +/// class unexpected_type, std::exception_ptr specialization (P0323R2) + +#if !nsel_CONFIG_NO_EXCEPTIONS +#if nsel_P0323R <= 2 + +// TODO: Should expected be specialized for particular E types such as exception_ptr and how? +// See p0323r7 2.1. Ergonomics, http://wg21.link/p0323 +template<> +class unexpected_type< std::exception_ptr > +{ +public: + using error_type = std::exception_ptr; + + unexpected_type() = delete; + + ~unexpected_type(){} + + explicit unexpected_type( std::exception_ptr const & error ) + : m_error( error ) + {} + + explicit unexpected_type(std::exception_ptr && error ) + : m_error( std::move( error ) ) + {} + + template< typename E > + explicit unexpected_type( E error ) + : m_error( std::make_exception_ptr( error ) ) + {} + + std::exception_ptr const & value() const + { + return m_error; + } + + std::exception_ptr & value() + { + return m_error; + } + +private: + std::exception_ptr m_error; +}; + +#endif // nsel_P0323R +#endif // !nsel_CONFIG_NO_EXCEPTIONS + +/// x.x.4, Unexpected equality operators + +template< typename E1, typename E2 > +constexpr bool operator==( unexpected_type const & x, unexpected_type const & y ) +{ + return x.value() == y.value(); +} + +template< typename E1, typename E2 > +constexpr bool operator!=( unexpected_type const & x, unexpected_type const & y ) +{ + return ! ( x == y ); +} + +#if nsel_P0323R <= 2 + +template< typename E > +constexpr bool operator<( unexpected_type const & x, unexpected_type const & y ) +{ + return x.value() < y.value(); +} + +template< typename E > +constexpr bool operator>( unexpected_type const & x, unexpected_type const & y ) +{ + return ( y < x ); +} + +template< typename E > +constexpr bool operator<=( unexpected_type const & x, unexpected_type const & y ) +{ + return ! ( y < x ); +} + +template< typename E > +constexpr bool operator>=( unexpected_type const & x, unexpected_type const & y ) +{ + return ! ( x < y ); +} + +#endif // nsel_P0323R + +/// x.x.5 Specialized algorithms + +template< typename E + nsel_REQUIRES_T( + std17::is_swappable::value + ) +> +void swap( unexpected_type & x, unexpected_type & y) noexcept ( noexcept ( x.swap(y) ) ) +{ + x.swap( y ); +} + +#if nsel_P0323R <= 2 + +// unexpected: relational operators for std::exception_ptr: + +inline constexpr bool operator<( unexpected_type const & /*x*/, unexpected_type const & /*y*/ ) +{ + return false; +} + +inline constexpr bool operator>( unexpected_type const & /*x*/, unexpected_type const & /*y*/ ) +{ + return false; +} + +inline constexpr bool operator<=( unexpected_type const & x, unexpected_type const & y ) +{ + return ( x == y ); +} + +inline constexpr bool operator>=( unexpected_type const & x, unexpected_type const & y ) +{ + return ( x == y ); +} + +#endif // nsel_P0323R + +// unexpected: traits + +#if nsel_P0323R <= 3 + +template< typename E> +struct is_unexpected : std::false_type {}; + +template< typename E> +struct is_unexpected< unexpected_type > : std::true_type {}; + +#endif // nsel_P0323R + +// unexpected: factory + +// keep make_unexpected() removed in p0323r2 for pre-C++17: + +template< typename E> +nsel_constexpr14 auto +make_unexpected( E && value ) -> unexpected_type< typename std::decay::type > +{ + return unexpected_type< typename std::decay::type >( std::forward(value) ); +} + +#if nsel_P0323R <= 3 + +/*nsel_constexpr14*/ auto inline +make_unexpected_from_current_exception() -> unexpected_type< std::exception_ptr > +{ + return unexpected_type< std::exception_ptr >( std::current_exception() ); +} + +#endif // nsel_P0323R + +/// x.x.6, x.x.7 expected access error + +template< typename E > +class bad_expected_access; + +/// x.x.7 bad_expected_access: expected access error + +template <> +class bad_expected_access< void > : public std::exception +{ +public: + explicit bad_expected_access() + : std::exception() + {} +}; + +/// x.x.6 bad_expected_access: expected access error + +#if !nsel_CONFIG_NO_EXCEPTIONS + +template< typename E > +class bad_expected_access : public bad_expected_access< void > +{ +public: + using error_type = E; + + explicit bad_expected_access( error_type error ) + : m_error( error ) + {} + + virtual char const * what() const noexcept override + { + return "bad_expected_access"; + } + + nsel_constexpr14 error_type & error() & + { + return m_error; + } + + constexpr error_type const & error() const & + { + return m_error; + } + +#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490 + + nsel_constexpr14 error_type && error() && + { + return std::move( m_error ); + } + + constexpr error_type const && error() const && + { + return std::move( m_error ); + } + +#endif + +private: + error_type m_error; +}; + +#endif // nsel_CONFIG_NO_EXCEPTIONS + +/// x.x.8 unexpect tag, in_place_unexpected tag: construct an error + +struct unexpect_t{}; +using in_place_unexpected_t = unexpect_t; + +nsel_inline17 constexpr unexpect_t unexpect{}; +nsel_inline17 constexpr unexpect_t in_place_unexpected{}; + +/// class error_traits + +#if nsel_CONFIG_NO_EXCEPTIONS + +namespace detail { + inline bool text( char const * /*text*/ ) { return true; } +} + +template< typename Error > +struct error_traits +{ + static void rethrow( Error const & /*e*/ ) + { +#if nsel_CONFIG_NO_EXCEPTIONS_SEH + RaiseException( EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE, 0, NULL ); +#else + assert( false && detail::text("throw bad_expected_access{ e };") ); +#endif + } +}; + +template<> +struct error_traits< std::exception_ptr > +{ + static void rethrow( std::exception_ptr const & /*e*/ ) + { +#if nsel_CONFIG_NO_EXCEPTIONS_SEH + RaiseException( EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE, 0, NULL ); +#else + assert( false && detail::text("throw bad_expected_access{ e };") ); +#endif + } +}; + +template<> +struct error_traits< std::error_code > +{ + static void rethrow( std::error_code const & /*e*/ ) + { +#if nsel_CONFIG_NO_EXCEPTIONS_SEH + RaiseException( EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE, 0, NULL ); +#else + assert( false && detail::text("throw std::system_error( e );") ); +#endif + } +}; + +#else // nsel_CONFIG_NO_EXCEPTIONS + +template< typename Error > +struct error_traits +{ + static void rethrow( Error const & e ) + { + throw bad_expected_access{ e }; + } +}; + +template<> +struct error_traits< std::exception_ptr > +{ + static void rethrow( std::exception_ptr const & e ) + { + std::rethrow_exception( e ); + } +}; + +template<> +struct error_traits< std::error_code > +{ + static void rethrow( std::error_code const & e ) + { + throw std::system_error( e ); + } +}; + +#endif // nsel_CONFIG_NO_EXCEPTIONS + +#if nsel_P2505R >= 3 +namespace detail { + +// from https://en.cppreference.com/w/cpp/utility/expected/unexpected: +// "the type of the unexpected value. The type must not be an array type, a non-object type, a specialization of std::unexpected, or a cv-qualified type." +template< typename T > +struct valid_unexpected_type : std::integral_constant< bool, + std::is_same< T, typename std20::remove_cvref< T >::type >::value + && std::is_object< T >::value + && !std::is_array< T >::value +> {}; + +template< typename T > +struct valid_unexpected_type< unexpected_type< T > > : std::false_type {}; + +} // namespace detail +#endif // nsel_P2505R >= 3 + +} // namespace expected_lite + +// provide nonstd::unexpected_type: + +using expected_lite::unexpected_type; + +namespace expected_lite { + +/// class expected + +#if nsel_P0323R <= 2 +template< typename T, typename E = std::exception_ptr > +class expected +#else +template< typename T, typename E > +class expected +#endif // nsel_P0323R +{ +private: + template< typename, typename > friend class expected; + +public: + using value_type = T; + using error_type = E; + using unexpected_type = nonstd::unexpected_type; + + template< typename U > + struct rebind + { + using type = expected; + }; + + // x.x.4.1 constructors + + nsel_REQUIRES_0( + std::is_default_constructible::value + ) + nsel_constexpr14 expected() + : contained( true ) + { + contained.construct_value(); + } + + nsel_constexpr14 expected( expected const & ) = default; + nsel_constexpr14 expected( expected && ) = default; + + template< typename U, typename G + nsel_REQUIRES_T( + std::is_constructible< T, U const &>::value + && std::is_constructible::value + && !std::is_constructible & >::value + && !std::is_constructible && >::value + && !std::is_constructible const & >::value + && !std::is_constructible const && >::value + && !std::is_convertible< expected & , T>::value + && !std::is_convertible< expected &&, T>::value + && !std::is_convertible< expected const & , T>::value + && !std::is_convertible< expected const &&, T>::value + && (!std::is_convertible::value || !std::is_convertible::value ) /*=> explicit */ + ) + > + nsel_constexpr14 explicit expected( expected const & other ) + : contained( other.has_value() ) + { + if ( has_value() ) contained.construct_value( T{ other.contained.value() } ); + else contained.construct_error( E{ other.contained.error() } ); + } + + template< typename U, typename G + nsel_REQUIRES_T( + std::is_constructible< T, U const &>::value + && std::is_constructible::value + && !std::is_constructible & >::value + && !std::is_constructible && >::value + && !std::is_constructible const & >::value + && !std::is_constructible const && >::value + && !std::is_convertible< expected & , T>::value + && !std::is_convertible< expected &&, T>::value + && !std::is_convertible< expected const &, T>::value + && !std::is_convertible< expected const &&, T>::value + && !(!std::is_convertible::value || !std::is_convertible::value ) /*=> non-explicit */ + ) + > + nsel_constexpr14 /*non-explicit*/ expected( expected const & other ) + : contained( other.has_value() ) + { + if ( has_value() ) contained.construct_value( other.contained.value() ); + else contained.construct_error( other.contained.error() ); + } + + template< typename U, typename G + nsel_REQUIRES_T( + std::is_constructible< T, U>::value + && std::is_constructible::value + && !std::is_constructible & >::value + && !std::is_constructible && >::value + && !std::is_constructible const & >::value + && !std::is_constructible const && >::value + && !std::is_convertible< expected & , T>::value + && !std::is_convertible< expected &&, T>::value + && !std::is_convertible< expected const & , T>::value + && !std::is_convertible< expected const &&, T>::value + && (!std::is_convertible::value || !std::is_convertible::value ) /*=> explicit */ + ) + > + nsel_constexpr14 explicit expected( expected && other ) + : contained( other.has_value() ) + { + if ( has_value() ) contained.construct_value( T{ std::move( other.contained.value() ) } ); + else contained.construct_error( E{ std::move( other.contained.error() ) } ); + } + + template< typename U, typename G + nsel_REQUIRES_T( + std::is_constructible< T, U>::value + && std::is_constructible::value + && !std::is_constructible & >::value + && !std::is_constructible && >::value + && !std::is_constructible const & >::value + && !std::is_constructible const && >::value + && !std::is_convertible< expected & , T>::value + && !std::is_convertible< expected &&, T>::value + && !std::is_convertible< expected const & , T>::value + && !std::is_convertible< expected const &&, T>::value + && !(!std::is_convertible::value || !std::is_convertible::value ) /*=> non-explicit */ + ) + > + nsel_constexpr14 /*non-explicit*/ expected( expected && other ) + : contained( other.has_value() ) + { + if ( has_value() ) contained.construct_value( std::move( other.contained.value() ) ); + else contained.construct_error( std::move( other.contained.error() ) ); + } + + template< typename U = T + nsel_REQUIRES_T( + std::is_copy_constructible::value + ) + > + nsel_constexpr14 expected( value_type const & value ) + : contained( true ) + { + contained.construct_value( value ); + } + + template< typename U = T + nsel_REQUIRES_T( + std::is_constructible::value + && !std::is_same::type, nonstd_lite_in_place_t(U)>::value + && !std::is_same< expected , typename std20::remove_cvref::type>::value + && !std::is_same, typename std20::remove_cvref::type>::value + && !std::is_convertible::value /*=> explicit */ + ) + > + nsel_constexpr14 explicit expected( U && value ) noexcept + ( + std::is_nothrow_move_constructible::value && + std::is_nothrow_move_constructible::value + ) + : contained( true ) + { + contained.construct_value( T{ std::forward( value ) } ); + } + + template< typename U = T + nsel_REQUIRES_T( + std::is_constructible::value + && !std::is_same::type, nonstd_lite_in_place_t(U)>::value + && !std::is_same< expected , typename std20::remove_cvref::type>::value + && !std::is_same, typename std20::remove_cvref::type>::value + && std::is_convertible::value /*=> non-explicit */ + ) + > + nsel_constexpr14 /*non-explicit*/ expected( U && value ) noexcept + ( + std::is_nothrow_move_constructible::value && + std::is_nothrow_move_constructible::value + ) + : contained( true ) + { + contained.construct_value( std::forward( value ) ); + } + + // construct error: + + template< typename G = E + nsel_REQUIRES_T( + std::is_constructible::value + && !std::is_convertible< G const &, E>::value /*=> explicit */ + ) + > + nsel_constexpr14 explicit expected( nonstd::unexpected_type const & error ) + : contained( false ) + { + contained.construct_error( E{ error.value() } ); + } + + template< typename G = E + nsel_REQUIRES_T( + std::is_constructible::value + && std::is_convertible< G const &, E>::value /*=> non-explicit */ + ) + > + nsel_constexpr14 /*non-explicit*/ expected( nonstd::unexpected_type const & error ) + : contained( false ) + { + contained.construct_error( error.value() ); + } + + template< typename G = E + nsel_REQUIRES_T( + std::is_constructible::value + && !std::is_convertible< G&&, E>::value /*=> explicit */ + ) + > + nsel_constexpr14 explicit expected( nonstd::unexpected_type && error ) + : contained( false ) + { + contained.construct_error( E{ std::move( error.value() ) } ); + } + + template< typename G = E + nsel_REQUIRES_T( + std::is_constructible::value + && std::is_convertible< G&&, E>::value /*=> non-explicit */ + ) + > + nsel_constexpr14 /*non-explicit*/ expected( nonstd::unexpected_type && error ) + : contained( false ) + { + contained.construct_error( std::move( error.value() ) ); + } + + // in-place construction, value + + template< typename... Args + nsel_REQUIRES_T( + std::is_constructible::value + ) + > + nsel_constexpr14 explicit expected( nonstd_lite_in_place_t(T), Args&&... args ) + : contained( true ) + { + contained.emplace_value( std::forward( args )... ); + } + + template< typename U, typename... Args + nsel_REQUIRES_T( + std::is_constructible, Args&&...>::value + ) + > + nsel_constexpr14 explicit expected( nonstd_lite_in_place_t(T), std::initializer_list il, Args&&... args ) + : contained( true ) + { + contained.emplace_value( il, std::forward( args )... ); + } + + // in-place construction, error + + template< typename... Args + nsel_REQUIRES_T( + std::is_constructible::value + ) + > + nsel_constexpr14 explicit expected( unexpect_t, Args&&... args ) + : contained( false ) + { + contained.emplace_error( std::forward( args )... ); + } + + template< typename U, typename... Args + nsel_REQUIRES_T( + std::is_constructible, Args&&...>::value + ) + > + nsel_constexpr14 explicit expected( unexpect_t, std::initializer_list il, Args&&... args ) + : contained( false ) + { + contained.emplace_error( il, std::forward( args )... ); + } + + // x.x.4.2 destructor + + // TODO: ~expected: triviality + // Effects: If T is not cv void and is_trivially_destructible_v is false and bool(*this), calls val.~T(). If is_trivially_destructible_v is false and !bool(*this), calls unexpect.~unexpected(). + // Remarks: If either T is cv void or is_trivially_destructible_v is true, and is_trivially_destructible_v is true, then this destructor shall be a trivial destructor. + + ~expected() + { + if ( has_value() ) contained.destruct_value(); + else contained.destruct_error(); + } + + // x.x.4.3 assignment + + expected & operator=( expected const & other ) + { + expected( other ).swap( *this ); + return *this; + } + + expected & operator=( expected && other ) noexcept + ( + std::is_nothrow_move_constructible< T>::value + && std::is_nothrow_move_assignable< T>::value + && std::is_nothrow_move_constructible::value // added for missing + && std::is_nothrow_move_assignable< E>::value ) // nothrow above + { + expected( std::move( other ) ).swap( *this ); + return *this; + } + + template< typename U + nsel_REQUIRES_T( + !std::is_same, typename std20::remove_cvref::type>::value + && std17::conjunction, std::is_same> >::value + && std::is_constructible::value + && std::is_assignable< T&,U>::value + && std::is_nothrow_move_constructible::value ) + > + expected & operator=( U && value ) + { + expected( std::forward( value ) ).swap( *this ); + return *this; + } + + template< typename G = E + nsel_REQUIRES_T( + std::is_constructible::value && + std::is_copy_constructible::value // TODO: std::is_nothrow_copy_constructible + && std::is_copy_assignable::value + ) + > + expected & operator=( nonstd::unexpected_type const & error ) + { + expected( unexpect, error.value() ).swap( *this ); + return *this; + } + + template< typename G = E + nsel_REQUIRES_T( + std::is_constructible::value && + std::is_move_constructible::value // TODO: std::is_nothrow_move_constructible + && std::is_move_assignable::value + ) + > + expected & operator=( nonstd::unexpected_type && error ) + { + expected( unexpect, std::move( error.value() ) ).swap( *this ); + return *this; + } + + template< typename... Args + nsel_REQUIRES_T( + std::is_nothrow_constructible::value + ) + > + value_type & emplace( Args &&... args ) + { + expected( nonstd_lite_in_place(T), std::forward(args)... ).swap( *this ); + return value(); + } + + template< typename U, typename... Args + nsel_REQUIRES_T( + std::is_nothrow_constructible&, Args&&...>::value + ) + > + value_type & emplace( std::initializer_list il, Args &&... args ) + { + expected( nonstd_lite_in_place(T), il, std::forward(args)... ).swap( *this ); + return value(); + } + + // x.x.4.4 swap + + template< typename U=T, typename G=E > + nsel_REQUIRES_R( void, + std17::is_swappable< U>::value + && std17::is_swappable::value + && ( std::is_move_constructible::value || std::is_move_constructible::value ) + ) + swap( expected & other ) noexcept + ( + std::is_nothrow_move_constructible::value && std17::is_nothrow_swappable::value && + std::is_nothrow_move_constructible::value && std17::is_nothrow_swappable::value + ) + { + using std::swap; + + if ( bool(*this) && bool(other) ) { swap( contained.value(), other.contained.value() ); } + else if ( ! bool(*this) && ! bool(other) ) { swap( contained.error(), other.contained.error() ); } + else if ( bool(*this) && ! bool(other) ) { error_type t( std::move( other.error() ) ); + other.contained.destruct_error(); + other.contained.construct_value( std::move( contained.value() ) ); + contained.destruct_value(); + contained.construct_error( std::move( t ) ); + bool has_value = contained.has_value(); + bool other_has_value = other.has_value(); + other.contained.set_has_value(has_value); + contained.set_has_value(other_has_value); + } + else if ( ! bool(*this) && bool(other) ) { other.swap( *this ); } + } + + // x.x.4.5 observers + + constexpr value_type const * operator ->() const + { + return assert( has_value() ), contained.value_ptr(); + } + + value_type * operator ->() + { + return assert( has_value() ), contained.value_ptr(); + } + + constexpr value_type const & operator *() const & + { + return assert( has_value() ), contained.value(); + } + + value_type & operator *() & + { + return assert( has_value() ), contained.value(); + } + +#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490 + + constexpr value_type const && operator *() const && + { + return std::move( ( assert( has_value() ), contained.value() ) ); + } + + nsel_constexpr14 value_type && operator *() && + { + return std::move( ( assert( has_value() ), contained.value() ) ); + } + +#endif + + constexpr explicit operator bool() const noexcept + { + return has_value(); + } + + constexpr bool has_value() const noexcept + { + return contained.has_value(); + } + + constexpr value_type const & value() const & + { + return has_value() + ? ( contained.value() ) + : ( error_traits::rethrow( contained.error() ), contained.value() ); + } + + value_type & value() & + { + return has_value() + ? ( contained.value() ) + : ( error_traits::rethrow( contained.error() ), contained.value() ); + } + +#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490 + + constexpr value_type const && value() const && + { + return std::move( has_value() + ? ( contained.value() ) + : ( error_traits::rethrow( contained.error() ), contained.value() ) ); + } + + nsel_constexpr14 value_type && value() && + { + return std::move( has_value() + ? ( contained.value() ) + : ( error_traits::rethrow( contained.error() ), contained.value() ) ); + } + +#endif + + constexpr error_type const & error() const & + { + return assert( ! has_value() ), contained.error(); + } + + error_type & error() & + { + return assert( ! has_value() ), contained.error(); + } + +#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490 + + constexpr error_type const && error() const && + { + return std::move( ( assert( ! has_value() ), contained.error() ) ); + } + + error_type && error() && + { + return std::move( ( assert( ! has_value() ), contained.error() ) ); + } + +#endif + + constexpr unexpected_type get_unexpected() const + { + return make_unexpected( contained.error() ); + } + + template< typename Ex > + bool has_exception() const + { + using ContainedEx = typename std::remove_reference< decltype( get_unexpected().value() ) >::type; + return ! has_value() && std::is_base_of< Ex, ContainedEx>::value; + } + + template< typename U + nsel_REQUIRES_T( + std::is_copy_constructible< T>::value + && std::is_convertible::value + ) + > + value_type value_or( U && v ) const & + { + return has_value() + ? contained.value() + : static_cast( std::forward( v ) ); + } + + template< typename U + nsel_REQUIRES_T( + std::is_move_constructible< T>::value + && std::is_convertible::value + ) + > + value_type value_or( U && v ) && + { + return has_value() + ? std::move( contained.value() ) + : static_cast( std::forward( v ) ); + } + +#if nsel_P2505R >= 4 + template< typename G = E + nsel_REQUIRES_T( + std::is_copy_constructible< E >::value + && std::is_convertible< G, E >::value + ) + > + nsel_constexpr error_type error_or( G && e ) const & + { + return has_value() + ? static_cast< E >( std::forward< G >( e ) ) + : contained.error(); + } + + template< typename G = E + nsel_REQUIRES_T( + std::is_move_constructible< E >::value + && std::is_convertible< G, E >::value + ) + > + nsel_constexpr14 error_type error_or( G && e ) && + { + return has_value() + ? static_cast< E >( std::forward< G >( e ) ) + : std::move( contained.error() ); + } +#endif // nsel_P2505R >= 4 + +#if nsel_P2505R >= 3 + // Monadic operations (P2505) + template< typename F + nsel_REQUIRES_T( + detail::is_expected < detail::invoke_result_nocvref_t< F, value_type & > > ::value + && std::is_same< typename detail::invoke_result_nocvref_t< F, value_type & >::error_type, error_type >::value + && std::is_constructible< error_type, error_type & >::value + ) + > + nsel_constexpr14 detail::invoke_result_nocvref_t< F, value_type & > and_then( F && f ) & + { + return has_value() + ? detail::invoke_result_nocvref_t< F, value_type & >( detail::invoke( std::forward< F >( f ), value() ) ) + : detail::invoke_result_nocvref_t< F, value_type & >( unexpect, error() ); + } + + template >::value + && std::is_same< typename detail::invoke_result_nocvref_t< F, const value_type & >::error_type, error_type >::value + && std::is_constructible< error_type, const error_type & >::value + ) + > + nsel_constexpr detail::invoke_result_nocvref_t< F, const value_type & > and_then( F && f ) const & + { + return has_value() + ? detail::invoke_result_nocvref_t< F, const value_type & >( detail::invoke( std::forward< F >( f ), value() ) ) + : detail::invoke_result_nocvref_t< F, const value_type & >( unexpect, error() ); + } + +#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490 + template >::value + && std::is_same< typename detail::invoke_result_nocvref_t< F, value_type && >::error_type, error_type >::value + && std::is_constructible< error_type, error_type && >::value + ) + > + nsel_constexpr14 detail::invoke_result_nocvref_t< F, value_type && > and_then( F && f ) && + { + return has_value() + ? detail::invoke_result_nocvref_t< F, value_type && >( detail::invoke( std::forward< F >( f ), std::move( value() ) ) ) + : detail::invoke_result_nocvref_t< F, value_type && >( unexpect, std::move( error() ) ); + } + + template >::value + && std::is_same< typename detail::invoke_result_nocvref_t< F, const value_type & >::error_type, error_type >::value + && std::is_constructible< error_type, const error_type && >::value + ) + > + nsel_constexpr detail::invoke_result_nocvref_t< F, const value_type && > and_then( F && f ) const && + { + return has_value() + ? detail::invoke_result_nocvref_t< F, const value_type && >( detail::invoke( std::forward< F >( f ), std::move( value() ) ) ) + : detail::invoke_result_nocvref_t< F, const value_type && >( unexpect, std::move( error() ) ); + } +#endif + + template >::value + && std::is_same< typename detail::invoke_result_nocvref_t< F, error_type & >::value_type, value_type >::value + && std::is_constructible< value_type, value_type & >::value + ) + > + nsel_constexpr14 detail::invoke_result_nocvref_t< F, error_type & > or_else( F && f ) & + { + return has_value() + ? detail::invoke_result_nocvref_t< F, error_type & >( value() ) + : detail::invoke_result_nocvref_t< F, error_type & >( detail::invoke( std::forward< F >( f ), error() ) ); + } + + template >::value + && std::is_same< typename detail::invoke_result_nocvref_t< F, const error_type & >::value_type, value_type >::value + && std::is_constructible< value_type, const value_type & >::value + ) + > + nsel_constexpr detail::invoke_result_nocvref_t< F, const error_type & > or_else( F && f ) const & + { + return has_value() + ? detail::invoke_result_nocvref_t< F, const error_type & >( value() ) + : detail::invoke_result_nocvref_t< F, const error_type & >( detail::invoke( std::forward< F >( f ), error() ) ); + } + +#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490 + template >::value + && std::is_same< typename detail::invoke_result_nocvref_t< F, error_type && >::value_type, value_type >::value + && std::is_constructible< value_type, value_type && >::value + ) + > + nsel_constexpr14 detail::invoke_result_nocvref_t< F, error_type && > or_else( F && f ) && + { + return has_value() + ? detail::invoke_result_nocvref_t< F, error_type && >( std::move( value() ) ) + : detail::invoke_result_nocvref_t< F, error_type && >( detail::invoke( std::forward< F >( f ), std::move( error() ) ) ); + } + + template >::value + && std::is_same< typename detail::invoke_result_nocvref_t< F, const error_type && >::value_type, value_type >::value + && std::is_constructible< value_type, const value_type && >::value + ) + > + nsel_constexpr detail::invoke_result_nocvref_t< F, const error_type && > or_else( F && f ) const && + { + return has_value() + ? detail::invoke_result_nocvref_t< F, const error_type && >( std::move( value() ) ) + : detail::invoke_result_nocvref_t< F, const error_type && >( detail::invoke( std::forward< F >( f ), std::move( error() ) ) ); + } +#endif + + template::value + && !std::is_void< detail::transform_invoke_result_t< F, value_type & > >::value + && detail::valid_expected_value_type< detail::transform_invoke_result_t< F, value_type & > >::value + ) + > + nsel_constexpr14 expected< detail::transform_invoke_result_t< F, value_type & >, error_type > transform( F && f ) & + { + return has_value() + ? expected< detail::transform_invoke_result_t< F, value_type & >, error_type >( detail::invoke( std::forward< F >( f ), **this ) ) + : make_unexpected( error() ); + } + + template::value + && std::is_void< detail::transform_invoke_result_t< F, value_type & > >::value + ) + > + nsel_constexpr14 expected< void, error_type > transform( F && f ) & + { + return has_value() + ? ( detail::invoke( std::forward< F >( f ), **this ), expected< void, error_type >() ) + : make_unexpected( error() ); + } + + template::value + && !std::is_void< detail::transform_invoke_result_t< F, const value_type & > >::value + && detail::valid_expected_value_type< detail::transform_invoke_result_t< F, const value_type & > >::value + ) + > + nsel_constexpr expected< detail::transform_invoke_result_t< F, const value_type & >, error_type > transform( F && f ) const & + { + return has_value() + ? expected< detail::transform_invoke_result_t< F, const value_type & >, error_type >( detail::invoke( std::forward< F >( f ), **this ) ) + : make_unexpected( error() ); + } + + template::value + && std::is_void< detail::transform_invoke_result_t< F, const value_type & > >::value + ) + > + nsel_constexpr expected< void, error_type > transform( F && f ) const & + { + return has_value() + ? ( detail::invoke( std::forward< F >( f ), **this ), expected< void, error_type >() ) + : make_unexpected( error() ); + } + +#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490 + template::value + && !std::is_void< detail::transform_invoke_result_t< F, value_type && > >::value + && detail::valid_expected_value_type< detail::transform_invoke_result_t< F, value_type && > >::value + ) + > + nsel_constexpr14 expected< detail::transform_invoke_result_t< F, value_type && >, error_type > transform( F && f ) && + { + return has_value() + ? expected< detail::transform_invoke_result_t< F, value_type && >, error_type >( detail::invoke( std::forward< F >( f ), std::move( **this ) ) ) + : make_unexpected( std::move( error() ) ); + } + + template::value + && std::is_void< detail::transform_invoke_result_t< F, value_type && > >::value + ) + > + nsel_constexpr14 expected< void, error_type > transform( F && f ) && + { + return has_value() + ? ( detail::invoke( std::forward< F >( f ), **this ), expected< void, error_type >() ) + : make_unexpected( std::move( error() ) ); + } + + template::value + && !std::is_void< detail::transform_invoke_result_t< F, const value_type && > >::value + && detail::valid_expected_value_type< detail::transform_invoke_result_t< F, const value_type && > >::value + ) + > + nsel_constexpr expected< detail::transform_invoke_result_t< F, const value_type && >, error_type > transform( F && f ) const && + { + return has_value() + ? expected< detail::transform_invoke_result_t< F, const value_type && >, error_type >( detail::invoke( std::forward< F >( f ), std::move( **this ) ) ) + : make_unexpected( std::move( error() ) ); + } + + template::value + && std::is_void< detail::transform_invoke_result_t< F, const value_type && > >::value + ) + > + nsel_constexpr expected< void, error_type > transform( F && f ) const && + { + return has_value() + ? ( detail::invoke( std::forward< F >( f ), **this ), expected< void, error_type >() ) + : make_unexpected( std::move( error() ) ); + } +#endif + + template >::value + && std::is_constructible< value_type, value_type & >::value + ) + > + nsel_constexpr14 expected< value_type, detail::transform_invoke_result_t< F, error_type & > > transform_error( F && f ) & + { + return has_value() + ? expected< value_type, detail::transform_invoke_result_t< F, error_type & > >( in_place, **this ) + : make_unexpected( detail::invoke( std::forward< F >( f ), error() ) ); + } + + template >::value + && std::is_constructible< value_type, const value_type & >::value + ) + > + nsel_constexpr expected< value_type, detail::transform_invoke_result_t< F, const error_type & > > transform_error( F && f ) const & + { + return has_value() + ? expected< value_type, detail::transform_invoke_result_t< F, const error_type & > >( in_place, **this ) + : make_unexpected( detail::invoke( std::forward< F >( f ), error() ) ); + } + +#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490 + template >::value + && std::is_constructible< value_type, value_type && >::value + ) + > + nsel_constexpr14 expected< value_type, detail::transform_invoke_result_t< F, error_type && > > transform_error( F && f ) && + { + return has_value() + ? expected< value_type, detail::transform_invoke_result_t< F, error_type && > >( in_place, std::move( **this ) ) + : make_unexpected( detail::invoke( std::forward< F >( f ), std::move( error() ) ) ); + } + + template >::value + && std::is_constructible< value_type, const value_type && >::value + ) + > + nsel_constexpr expected< value_type, detail::transform_invoke_result_t< F, const error_type && > > transform_error( F && f ) const && + { + return has_value() + ? expected< value_type, detail::transform_invoke_result_t< F, const error_type && > >( in_place, std::move( **this ) ) + : make_unexpected( detail::invoke( std::forward< F >( f ), std::move( error() ) ) ); + } +#endif +#endif // nsel_P2505R >= 3 + // unwrap() + +// template +// constexpr expected expected,E>::unwrap() const&; + +// template +// constexpr expected expected::unwrap() const&; + +// template +// expected expected, E>::unwrap() &&; + +// template +// template expected expected::unwrap() &&; + + // factories + +// template< typename Ex, typename F> +// expected catch_exception(F&& f); + +// template< typename F> +// expected())),E> map(F&& func) ; + +// template< typename F> +// 'see below' bind(F&& func); + +// template< typename F> +// expected catch_error(F&& f); + +// template< typename F> +// 'see below' then(F&& func); + +private: + detail::storage_t + < + T + ,E + , std::is_copy_constructible::value && std::is_copy_constructible::value + , std::is_move_constructible::value && std::is_move_constructible::value + > + contained; +}; + +/// class expected, void specialization + +template< typename E > +class expected +{ +private: + template< typename, typename > friend class expected; + +public: + using value_type = void; + using error_type = E; + using unexpected_type = nonstd::unexpected_type; + + // x.x.4.1 constructors + + constexpr expected() noexcept + : contained( true ) + {} + + nsel_constexpr14 expected( expected const & other ) = default; + nsel_constexpr14 expected( expected && other ) = default; + + constexpr explicit expected( nonstd_lite_in_place_t(void) ) + : contained( true ) + {} + + template< typename G = E + nsel_REQUIRES_T( + !std::is_convertible::value /*=> explicit */ + ) + > + nsel_constexpr14 explicit expected( nonstd::unexpected_type const & error ) + : contained( false ) + { + contained.construct_error( E{ error.value() } ); + } + + template< typename G = E + nsel_REQUIRES_T( + std::is_convertible::value /*=> non-explicit */ + ) + > + nsel_constexpr14 /*non-explicit*/ expected( nonstd::unexpected_type const & error ) + : contained( false ) + { + contained.construct_error( error.value() ); + } + + template< typename G = E + nsel_REQUIRES_T( + !std::is_convertible::value /*=> explicit */ + ) + > + nsel_constexpr14 explicit expected( nonstd::unexpected_type && error ) + : contained( false ) + { + contained.construct_error( E{ std::move( error.value() ) } ); + } + + template< typename G = E + nsel_REQUIRES_T( + std::is_convertible::value /*=> non-explicit */ + ) + > + nsel_constexpr14 /*non-explicit*/ expected( nonstd::unexpected_type && error ) + : contained( false ) + { + contained.construct_error( std::move( error.value() ) ); + } + + template< typename... Args + nsel_REQUIRES_T( + std::is_constructible::value + ) + > + nsel_constexpr14 explicit expected( unexpect_t, Args&&... args ) + : contained( false ) + { + contained.emplace_error( std::forward( args )... ); + } + + template< typename U, typename... Args + nsel_REQUIRES_T( + std::is_constructible, Args&&...>::value + ) + > + nsel_constexpr14 explicit expected( unexpect_t, std::initializer_list il, Args&&... args ) + : contained( false ) + { + contained.emplace_error( il, std::forward( args )... ); + } + + // destructor + + ~expected() + { + if ( ! has_value() ) + { + contained.destruct_error(); + } + } + + // x.x.4.3 assignment + + expected & operator=( expected const & other ) + { + expected( other ).swap( *this ); + return *this; + } + + expected & operator=( expected && other ) noexcept + ( + std::is_nothrow_move_assignable::value && + std::is_nothrow_move_constructible::value ) + { + expected( std::move( other ) ).swap( *this ); + return *this; + } + + void emplace() + { + expected().swap( *this ); + } + + // x.x.4.4 swap + + template< typename G = E > + nsel_REQUIRES_R( void, + std17::is_swappable::value + && std::is_move_constructible::value + ) + swap( expected & other ) noexcept + ( + std::is_nothrow_move_constructible::value && std17::is_nothrow_swappable::value + ) + { + using std::swap; + + if ( ! bool(*this) && ! bool(other) ) { swap( contained.error(), other.contained.error() ); } + else if ( bool(*this) && ! bool(other) ) { contained.construct_error( std::move( other.error() ) ); + bool has_value = contained.has_value(); + bool other_has_value = other.has_value(); + other.contained.set_has_value(has_value); + contained.set_has_value(other_has_value); + } + else if ( ! bool(*this) && bool(other) ) { other.swap( *this ); } + } + + // x.x.4.5 observers + + constexpr explicit operator bool() const noexcept + { + return has_value(); + } + + constexpr bool has_value() const noexcept + { + return contained.has_value(); + } + + void value() const + { + if ( ! has_value() ) + { + error_traits::rethrow( contained.error() ); + } + } + + constexpr error_type const & error() const & + { + return assert( ! has_value() ), contained.error(); + } + + error_type & error() & + { + return assert( ! has_value() ), contained.error(); + } + +#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490 + + constexpr error_type const && error() const && + { + return std::move( ( assert( ! has_value() ), contained.error() ) ); + } + + error_type && error() && + { + return std::move( ( assert( ! has_value() ), contained.error() ) ); + } + +#endif + + constexpr unexpected_type get_unexpected() const + { + return make_unexpected( contained.error() ); + } + + template< typename Ex > + bool has_exception() const + { + using ContainedEx = typename std::remove_reference< decltype( get_unexpected().value() ) >::type; + return ! has_value() && std::is_base_of< Ex, ContainedEx>::value; + } + +#if nsel_P2505R >= 4 + template< typename G = E + nsel_REQUIRES_T( + std::is_copy_constructible< E >::value + && std::is_convertible< G, E >::value + ) + > + nsel_constexpr error_type error_or( G && e ) const & + { + return has_value() + ? static_cast< E >( std::forward< G >( e ) ) + : contained.error(); + } + + template< typename G = E + nsel_REQUIRES_T( + std::is_move_constructible< E >::value + && std::is_convertible< G, E >::value + ) + > + nsel_constexpr14 error_type error_or( G && e ) && + { + return has_value() + ? static_cast< E >( std::forward< G >( e ) ) + : std::move( contained.error() ); + } +#endif // nsel_P2505R >= 4 + +#if nsel_P2505R >= 3 + // Monadic operations (P2505) + template >::value + && std::is_same< typename detail::invoke_result_nocvref_t< F >::error_type, error_type >::value + && std::is_constructible< error_type, error_type & >::value + ) + > + nsel_constexpr14 detail::invoke_result_nocvref_t< F > and_then( F && f ) & + { + return has_value() + ? detail::invoke_result_nocvref_t< F >( detail::invoke( std::forward< F >( f ) ) ) + : detail::invoke_result_nocvref_t< F >( unexpect, error() ); + } + + template >::value + && std::is_same< typename detail::invoke_result_nocvref_t< F >::error_type, error_type >::value + && std::is_constructible< error_type, const error_type & >::value + ) + > + nsel_constexpr detail::invoke_result_nocvref_t< F > and_then( F && f ) const & + { + return has_value() + ? detail::invoke_result_nocvref_t< F >( detail::invoke( std::forward< F >( f ) ) ) + : detail::invoke_result_nocvref_t< F >( unexpect, error() ); + } + +#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490 + template >::value + && std::is_same< typename detail::invoke_result_nocvref_t< F >::error_type, error_type >::value + && std::is_constructible< error_type, error_type && >::value + ) + > + nsel_constexpr14 detail::invoke_result_nocvref_t< F > and_then( F && f ) && + { + return has_value() + ? detail::invoke_result_nocvref_t< F >( detail::invoke( std::forward< F >( f ) ) ) + : detail::invoke_result_nocvref_t< F >( unexpect, std::move( error() ) ); + } + + template >::value + && std::is_same< typename detail::invoke_result_nocvref_t< F >::error_type, error_type >::value + && std::is_constructible< error_type, const error_type && >::value + ) + > + nsel_constexpr detail::invoke_result_nocvref_t< F > and_then( F && f ) const && + { + return has_value() + ? detail::invoke_result_nocvref_t< F >( detail::invoke( std::forward< F >( f ) ) ) + : detail::invoke_result_nocvref_t< F >( unexpect, std::move( error() ) ); + } +#endif + + template >::value + && std::is_void< typename detail::invoke_result_nocvref_t< F, error_type & >::value_type >::value + ) + > + nsel_constexpr14 detail::invoke_result_nocvref_t< F, error_type & > or_else( F && f ) & + { + return has_value() + ? detail::invoke_result_nocvref_t< F, error_type & >() + : detail::invoke_result_nocvref_t< F, error_type & >( detail::invoke( std::forward< F >( f ), error() ) ); + } + + template >::value + && std::is_void< typename detail::invoke_result_nocvref_t< F, const error_type & >::value_type >::value + ) + > + nsel_constexpr detail::invoke_result_nocvref_t< F, const error_type & > or_else( F && f ) const & + { + return has_value() + ? detail::invoke_result_nocvref_t< F, const error_type & >() + : detail::invoke_result_nocvref_t< F, const error_type & >( detail::invoke( std::forward< F >( f ), error() ) ); + } + +#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490 + template >::value + && std::is_void< typename detail::invoke_result_nocvref_t< F, error_type && >::value_type >::value + ) + > + nsel_constexpr14 detail::invoke_result_nocvref_t< F, error_type && > or_else( F && f ) && + { + return has_value() + ? detail::invoke_result_nocvref_t< F, error_type && >() + : detail::invoke_result_nocvref_t< F, error_type && >( detail::invoke( std::forward< F >( f ), std::move( error() ) ) ); + } + + template >::value + && std::is_void< typename detail::invoke_result_nocvref_t< F, const error_type && >::value_type >::value + ) + > + nsel_constexpr detail::invoke_result_nocvref_t< F, const error_type && > or_else( F && f ) const && + { + return has_value() + ? detail::invoke_result_nocvref_t< F, const error_type && >() + : detail::invoke_result_nocvref_t< F, const error_type && >( detail::invoke( std::forward< F >( f ), std::move( error() ) ) ); + } +#endif + + template::value + && !std::is_void< detail::transform_invoke_result_t< F > >::value + ) + > + nsel_constexpr14 expected< detail::transform_invoke_result_t< F >, error_type > transform( F && f ) & + { + return has_value() + ? expected< detail::transform_invoke_result_t< F >, error_type >( detail::invoke( std::forward< F >( f ) ) ) + : make_unexpected( error() ); + } + + template::value + && std::is_void< detail::transform_invoke_result_t< F > >::value + ) + > + nsel_constexpr14 expected< void, error_type > transform( F && f ) & + { + return has_value() + ? ( detail::invoke( std::forward< F >( f ) ), expected< void, error_type >() ) + : make_unexpected( error() ); + } + + template::value + && !std::is_void< detail::transform_invoke_result_t< F > >::value + ) + > + nsel_constexpr expected< detail::transform_invoke_result_t< F >, error_type > transform( F && f ) const & + { + return has_value() + ? expected< detail::transform_invoke_result_t< F >, error_type >( detail::invoke( std::forward< F >( f ) ) ) + : make_unexpected( error() ); + } + + template::value + && std::is_void< detail::transform_invoke_result_t< F > >::value + ) + > + nsel_constexpr expected< void, error_type > transform( F && f ) const & + { + return has_value() + ? ( detail::invoke( std::forward< F >( f ) ), expected< void, error_type >() ) + : make_unexpected( error() ); + } + +#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490 + template::value + && !std::is_void< detail::transform_invoke_result_t< F > >::value + ) + > + nsel_constexpr14 expected< detail::transform_invoke_result_t< F >, error_type > transform( F && f ) && + { + return has_value() + ? expected< detail::transform_invoke_result_t< F >, error_type >( detail::invoke( std::forward< F >( f ) ) ) + : make_unexpected( error() ); + } + + template::value + && std::is_void< detail::transform_invoke_result_t< F > >::value + ) + > + nsel_constexpr14 expected< void, error_type > transform( F && f ) && + { + return has_value() + ? ( detail::invoke( std::forward< F >( f ) ), expected< void, error_type >() ) + : make_unexpected( error() ); + } + + template::value + && !std::is_void< detail::transform_invoke_result_t< F > >::value + ) + > + nsel_constexpr expected< detail::transform_invoke_result_t< F >, error_type > transform( F && f ) const && + { + return has_value() + ? expected< detail::transform_invoke_result_t< F >, error_type >( detail::invoke( std::forward< F >( f ) ) ) + : make_unexpected( error() ); + } + + template::value + && std::is_void< detail::transform_invoke_result_t< F > >::value + ) + > + nsel_constexpr expected< void, error_type > transform( F && f ) const && + { + return has_value() + ? ( detail::invoke( std::forward< F >( f ) ), expected< void, error_type >() ) + : make_unexpected( error() ); + } +#endif + + template >::value + ) + > + nsel_constexpr14 expected< void, detail::transform_invoke_result_t< F, error_type & > > transform_error( F && f ) & + { + return has_value() + ? expected< void, detail::transform_invoke_result_t< F, error_type & > >() + : make_unexpected( detail::invoke( std::forward< F >( f ), error() ) ); + } + + template >::value + ) + > + nsel_constexpr expected< void, detail::transform_invoke_result_t< F, const error_type & > > transform_error( F && f ) const & + { + return has_value() + ? expected< void, detail::transform_invoke_result_t< F, const error_type & > >() + : make_unexpected( detail::invoke( std::forward< F >( f ), error() ) ); + } + +#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490 + template >::value + ) + > + nsel_constexpr14 expected< void, detail::transform_invoke_result_t< F, error_type && > > transform_error( F && f ) && + { + return has_value() + ? expected< void, detail::transform_invoke_result_t< F, error_type && > >() + : make_unexpected( detail::invoke( std::forward< F >( f ), std::move( error() ) ) ); + } + + template >::value + ) + > + nsel_constexpr expected< void, detail::transform_invoke_result_t< F, const error_type && > > transform_error( F && f ) const && + { + return has_value() + ? expected< void, detail::transform_invoke_result_t< F, const error_type && > >() + : make_unexpected( detail::invoke( std::forward< F >( f ), std::move( error() ) ) ); + } +#endif +#endif // nsel_P2505R >= 3 + +// template constexpr 'see below' unwrap() const&; +// +// template 'see below' unwrap() &&; + + // factories + +// template< typename Ex, typename F> +// expected catch_exception(F&& f); +// +// template< typename F> +// expected map(F&& func) ; +// +// template< typename F> +// 'see below' bind(F&& func) ; +// +// template< typename F> +// expected catch_error(F&& f); +// +// template< typename F> +// 'see below' then(F&& func); + +private: + detail::storage_t + < + void + , E + , std::is_copy_constructible::value + , std::is_move_constructible::value + > + contained; +}; + +// x.x.4.6 expected<>: comparison operators + +template< typename T1, typename E1, typename T2, typename E2 + nsel_REQUIRES_T( + !std::is_void::value && !std::is_void::value + ) +> +constexpr bool operator==( expected const & x, expected const & y ) +{ + return bool(x) != bool(y) ? false : bool(x) ? *x == *y : x.error() == y.error(); +} + +template< typename T1, typename E1, typename T2, typename E2 + nsel_REQUIRES_T( + std::is_void::value && std::is_void::value + ) +> +constexpr bool operator==( expected const & x, expected const & y ) +{ + return bool(x) != bool(y) ? false : bool(x) || static_cast( x.error() == y.error() ); +} + +template< typename T1, typename E1, typename T2, typename E2 > +constexpr bool operator!=( expected const & x, expected const & y ) +{ + return !(x == y); +} + +#if nsel_P0323R <= 2 + +template< typename T, typename E > +constexpr bool operator<( expected const & x, expected const & y ) +{ + return (!y) ? false : (!x) ? true : *x < *y; +} + +template< typename T, typename E > +constexpr bool operator>( expected const & x, expected const & y ) +{ + return (y < x); +} + +template< typename T, typename E > +constexpr bool operator<=( expected const & x, expected const & y ) +{ + return !(y < x); +} + +template< typename T, typename E > +constexpr bool operator>=( expected const & x, expected const & y ) +{ + return !(x < y); +} + +#endif + +// x.x.4.7 expected: comparison with T + +template< typename T1, typename E1, typename T2 + nsel_REQUIRES_T( + !std::is_void::value + ) +> +constexpr bool operator==( expected const & x, T2 const & v ) +{ + return bool(x) ? *x == v : false; +} + +template< typename T1, typename E1, typename T2 + nsel_REQUIRES_T( + !std::is_void::value + ) +> +constexpr bool operator==(T2 const & v, expected const & x ) +{ + return bool(x) ? v == *x : false; +} + +template< typename T1, typename E1, typename T2 > +constexpr bool operator!=( expected const & x, T2 const & v ) +{ + return bool(x) ? *x != v : true; +} + +template< typename T1, typename E1, typename T2 > +constexpr bool operator!=( T2 const & v, expected const & x ) +{ + return bool(x) ? v != *x : true; +} + +#if nsel_P0323R <= 2 + +template< typename T, typename E > +constexpr bool operator<( expected const & x, T const & v ) +{ + return bool(x) ? *x < v : true; +} + +template< typename T, typename E > +constexpr bool operator<( T const & v, expected const & x ) +{ + return bool(x) ? v < *x : false; +} + +template< typename T, typename E > +constexpr bool operator>( T const & v, expected const & x ) +{ + return bool(x) ? *x < v : false; +} + +template< typename T, typename E > +constexpr bool operator>( expected const & x, T const & v ) +{ + return bool(x) ? v < *x : false; +} + +template< typename T, typename E > +constexpr bool operator<=( T const & v, expected const & x ) +{ + return bool(x) ? ! ( *x < v ) : false; +} + +template< typename T, typename E > +constexpr bool operator<=( expected const & x, T const & v ) +{ + return bool(x) ? ! ( v < *x ) : true; +} + +template< typename T, typename E > +constexpr bool operator>=( expected const & x, T const & v ) +{ + return bool(x) ? ! ( *x < v ) : false; +} + +template< typename T, typename E > +constexpr bool operator>=( T const & v, expected const & x ) +{ + return bool(x) ? ! ( v < *x ) : true; +} + +#endif // nsel_P0323R + +// x.x.4.8 expected: comparison with unexpected_type + +template< typename T1, typename E1 , typename E2 > +constexpr bool operator==( expected const & x, unexpected_type const & u ) +{ + return (!x) ? x.get_unexpected() == u : false; +} + +template< typename T1, typename E1 , typename E2 > +constexpr bool operator==( unexpected_type const & u, expected const & x ) +{ + return ( x == u ); +} + +template< typename T1, typename E1 , typename E2 > +constexpr bool operator!=( expected const & x, unexpected_type const & u ) +{ + return ! ( x == u ); +} + +template< typename T1, typename E1 , typename E2 > +constexpr bool operator!=( unexpected_type const & u, expected const & x ) +{ + return ! ( x == u ); +} + +#if nsel_P0323R <= 2 + +template< typename T, typename E > +constexpr bool operator<( expected const & x, unexpected_type const & u ) +{ + return (!x) ? ( x.get_unexpected() < u ) : false; +} + +template< typename T, typename E > +constexpr bool operator<( unexpected_type const & u, expected const & x ) +{ + return (!x) ? ( u < x.get_unexpected() ) : true ; +} + +template< typename T, typename E > +constexpr bool operator>( expected const & x, unexpected_type const & u ) +{ + return ( u < x ); +} + +template< typename T, typename E > +constexpr bool operator>( unexpected_type const & u, expected const & x ) +{ + return ( x < u ); +} + +template< typename T, typename E > +constexpr bool operator<=( expected const & x, unexpected_type const & u ) +{ + return ! ( u < x ); +} + +template< typename T, typename E > +constexpr bool operator<=( unexpected_type const & u, expected const & x) +{ + return ! ( x < u ); +} + +template< typename T, typename E > +constexpr bool operator>=( expected const & x, unexpected_type const & u ) +{ + return ! ( u > x ); +} + +template< typename T, typename E > +constexpr bool operator>=( unexpected_type const & u, expected const & x ) +{ + return ! ( x > u ); +} + +#endif // nsel_P0323R + +/// x.x.x Specialized algorithms + +template< typename T, typename E + nsel_REQUIRES_T( + ( std::is_void::value || std::is_move_constructible::value ) + && std::is_move_constructible::value + && std17::is_swappable::value + && std17::is_swappable::value ) +> +void swap( expected & x, expected & y ) noexcept ( noexcept ( x.swap(y) ) ) +{ + x.swap( y ); +} + +#if nsel_P0323R <= 3 + +template< typename T > +constexpr auto make_expected( T && v ) -> expected< typename std::decay::type > +{ + return expected< typename std::decay::type >( std::forward( v ) ); +} + +// expected specialization: + +auto inline make_expected() -> expected +{ + return expected( in_place ); +} + +template< typename T > +constexpr auto make_expected_from_current_exception() -> expected +{ + return expected( make_unexpected_from_current_exception() ); +} + +template< typename T > +auto make_expected_from_exception( std::exception_ptr v ) -> expected +{ + return expected( unexpected_type( std::forward( v ) ) ); +} + +template< typename T, typename E > +constexpr auto make_expected_from_error( E e ) -> expected::type> +{ + return expected::type>( make_unexpected( e ) ); +} + +template< typename F + nsel_REQUIRES_T( ! std::is_same::type, void>::value ) +> +/*nsel_constexpr14*/ +auto make_expected_from_call( F f ) -> expected< typename std::result_of::type > +{ + try + { + return make_expected( f() ); + } + catch (...) + { + return make_unexpected_from_current_exception(); + } +} + +template< typename F + nsel_REQUIRES_T( std::is_same::type, void>::value ) +> +/*nsel_constexpr14*/ +auto make_expected_from_call( F f ) -> expected +{ + try + { + f(); + return make_expected(); + } + catch (...) + { + return make_unexpected_from_current_exception(); + } +} + +#endif // nsel_P0323R + +} // namespace expected_lite + +using namespace expected_lite; + +// using expected_lite::expected; +// using ... + +} // namespace nonstd + +namespace std { + +// expected: hash support + +template< typename T, typename E > +struct hash< nonstd::expected > +{ + using result_type = std::size_t; + using argument_type = nonstd::expected; + + constexpr result_type operator()(argument_type const & arg) const + { + return arg ? std::hash{}(*arg) : result_type{}; + } +}; + +// TBD - ?? remove? see spec. +template< typename T, typename E > +struct hash< nonstd::expected > +{ + using result_type = std::size_t; + using argument_type = nonstd::expected; + + constexpr result_type operator()(argument_type const & arg) const + { + return arg ? std::hash{}(*arg) : result_type{}; + } +}; + +// TBD - implement +// bool(e), hash>()(e) shall evaluate to the hashing true; +// otherwise it evaluates to an unspecified value if E is exception_ptr or +// a combination of hashing false and hash()(e.error()). + +template< typename E > +struct hash< nonstd::expected > +{ +}; + +} // namespace std + +namespace nonstd { + +// void unexpected() is deprecated && removed in C++17 + +#if nsel_CPP17_OR_GREATER || nsel_COMPILER_MSVC_VERSION > 141 +template< typename E > +using unexpected = unexpected_type; +#endif + +} // namespace nonstd + +#undef nsel_REQUIRES +#undef nsel_REQUIRES_0 +#undef nsel_REQUIRES_T + +nsel_RESTORE_WARNINGS() + +#endif // nsel_USES_STD_EXPECTED + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif // NONSTD_EXPECTED_LITE_HPP diff --git a/contrib/tinyusdz/tinyusdz_repo/src/nonstd/optional.hpp b/contrib/tinyusdz/tinyusdz_repo/src/nonstd/optional.hpp new file mode 100644 index 000000000..007bab95e --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/nonstd/optional.hpp @@ -0,0 +1,1791 @@ +// +// Copyright (c) 2014-2018 Martin Moene +// +// https://github.com/martinmoene/optional-lite +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#ifndef NONSTD_OPTIONAL_LITE_HPP +#define NONSTD_OPTIONAL_LITE_HPP + +#define optional_lite_MAJOR 3 +#define optional_lite_MINOR 3 +#define optional_lite_PATCH 0 + +#define optional_lite_VERSION optional_STRINGIFY(optional_lite_MAJOR) "." optional_STRINGIFY(optional_lite_MINOR) "." optional_STRINGIFY(optional_lite_PATCH) + +#define optional_STRINGIFY( x ) optional_STRINGIFY_( x ) +#define optional_STRINGIFY_( x ) #x + + +// optional-lite configuration: + +#define optional_OPTIONAL_DEFAULT 0 +#define optional_OPTIONAL_NONSTD 1 +#define optional_OPTIONAL_STD 2 + +// TinyUSDZ mod. Force use nonstd implementation even on C++17 to avoid possible linkage issue. +#if defined( optional_CONFIG_SELECT_OPTIONAL ) +#undef optional_CONFIG_SELECT_OPTIONAL +#endif +#define optional_CONFIG_SELECT_OPTIONAL (optional_OPTIONAL_NONSTD) + +// tweak header support: + +#ifdef __has_include +# if __has_include() +# include +# endif +#define optional_HAVE_TWEAK_HEADER 1 +#else +#define optional_HAVE_TWEAK_HEADER 0 +//# pragma message("optional.hpp: Note: Tweak header not supported.") +#endif + +// optional selection and configuration: + +#if !defined( optional_CONFIG_SELECT_OPTIONAL ) +# define optional_CONFIG_SELECT_OPTIONAL ( optional_HAVE_STD_OPTIONAL ? optional_OPTIONAL_STD : optional_OPTIONAL_NONSTD ) +#endif + +// Control presence of exception handling (try and auto discover): + +// TinyUSDZ mod. Force disable exception. +#if defined( optional_CONFIG_NO_EXCEPTIONS ) +#undef optional_CONFIG_NO_EXCEPTIONS +#define optional_CONFIG_NO_EXCEPTIONS 1 +#endif + +#ifndef optional_CONFIG_NO_EXCEPTIONS +# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) +# define optional_CONFIG_NO_EXCEPTIONS 0 +# else +# define optional_CONFIG_NO_EXCEPTIONS 1 +# endif +#endif + +// C++ language version detection (C++20 is speculative): +// Note: VC14.0/1900 (VS2015) lacks too much from C++14. + +#ifndef optional_CPLUSPLUS +# if defined(_MSVC_LANG ) && !defined(__clang__) +# define optional_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) +# else +# define optional_CPLUSPLUS __cplusplus +# endif +#endif + +#define optional_CPP98_OR_GREATER ( optional_CPLUSPLUS >= 199711L ) +#define optional_CPP11_OR_GREATER ( optional_CPLUSPLUS >= 201103L ) +#define optional_CPP11_OR_GREATER_ ( optional_CPLUSPLUS >= 201103L ) +#define optional_CPP14_OR_GREATER ( optional_CPLUSPLUS >= 201402L ) +#define optional_CPP17_OR_GREATER ( optional_CPLUSPLUS >= 201703L ) +#define optional_CPP20_OR_GREATER ( optional_CPLUSPLUS >= 202000L ) + +// C++ language version (represent 98 as 3): + +#define optional_CPLUSPLUS_V ( optional_CPLUSPLUS / 100 - (optional_CPLUSPLUS > 200000 ? 2000 : 1994) ) + +// Use C++17 std::optional if available and requested: + +#if optional_CPP17_OR_GREATER && defined(__has_include ) +# if __has_include( ) +# define optional_HAVE_STD_OPTIONAL 1 +# else +# define optional_HAVE_STD_OPTIONAL 0 +# endif +#else +# define optional_HAVE_STD_OPTIONAL 0 +#endif + +#define optional_USES_STD_OPTIONAL ( (optional_CONFIG_SELECT_OPTIONAL == optional_OPTIONAL_STD) || ((optional_CONFIG_SELECT_OPTIONAL == optional_OPTIONAL_DEFAULT) && optional_HAVE_STD_OPTIONAL) ) + +// +// in_place: code duplicated in any-lite, expected-lite, optional-lite, value-ptr-lite, variant-lite: +// + +#ifndef nonstd_lite_HAVE_IN_PLACE_TYPES +#define nonstd_lite_HAVE_IN_PLACE_TYPES 1 + +// C++17 std::in_place in : + +#if optional_CPP17_OR_GREATER + +#include + +namespace nonstd { + +using std::in_place; +using std::in_place_type; +using std::in_place_index; +using std::in_place_t; +using std::in_place_type_t; +using std::in_place_index_t; + +#define nonstd_lite_in_place_t( T) std::in_place_t +#define nonstd_lite_in_place_type_t( T) std::in_place_type_t +#define nonstd_lite_in_place_index_t(K) std::in_place_index_t + +#define nonstd_lite_in_place( T) std::in_place_t{} +#define nonstd_lite_in_place_type( T) std::in_place_type_t{} +#define nonstd_lite_in_place_index(K) std::in_place_index_t{} + +} // namespace nonstd + +#else // optional_CPP17_OR_GREATER + +#include + +namespace nonstd { +namespace detail { + +template< class T > +struct in_place_type_tag {}; + +template< std::size_t K > +struct in_place_index_tag {}; + +} // namespace detail + +struct in_place_t {}; + +template< class T > +inline in_place_t in_place( detail::in_place_type_tag /*unused*/ = detail::in_place_type_tag() ) +{ + return in_place_t(); +} + +template< std::size_t K > +inline in_place_t in_place( detail::in_place_index_tag /*unused*/ = detail::in_place_index_tag() ) +{ + return in_place_t(); +} + +template< class T > +inline in_place_t in_place_type( detail::in_place_type_tag /*unused*/ = detail::in_place_type_tag() ) +{ + return in_place_t(); +} + +template< std::size_t K > +inline in_place_t in_place_index( detail::in_place_index_tag /*unused*/ = detail::in_place_index_tag() ) +{ + return in_place_t(); +} + +// mimic templated typedef: + +#define nonstd_lite_in_place_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag ) +#define nonstd_lite_in_place_type_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag ) +#define nonstd_lite_in_place_index_t(K) nonstd::in_place_t(&)( nonstd::detail::in_place_index_tag ) + +#define nonstd_lite_in_place( T) nonstd::in_place_type +#define nonstd_lite_in_place_type( T) nonstd::in_place_type +#define nonstd_lite_in_place_index(K) nonstd::in_place_index + +} // namespace nonstd + +#endif // optional_CPP17_OR_GREATER +#endif // nonstd_lite_HAVE_IN_PLACE_TYPES + +// +// Using std::optional: +// + +#if optional_USES_STD_OPTIONAL + +#include + +namespace nonstd { + + using std::optional; + using std::bad_optional_access; + using std::hash; + + using std::nullopt; + using std::nullopt_t; + + using std::operator==; + using std::operator!=; + using std::operator<; + using std::operator<=; + using std::operator>; + using std::operator>=; + using std::make_optional; + using std::swap; +} + +#else // optional_USES_STD_OPTIONAL + +#include +#include + +// optional-lite alignment configuration: + +#ifndef optional_CONFIG_MAX_ALIGN_HACK +# define optional_CONFIG_MAX_ALIGN_HACK 0 +#endif + +#ifndef optional_CONFIG_ALIGN_AS +// no default, used in #if defined() +#endif + +#ifndef optional_CONFIG_ALIGN_AS_FALLBACK +# define optional_CONFIG_ALIGN_AS_FALLBACK double +#endif + +// Compiler warning suppression: + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wundef" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wundef" +#elif defined(_MSC_VER ) +# pragma warning( push ) +#endif + +// half-open range [lo..hi): +#define optional_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) ) + +// Compiler versions: +// +// MSVC++ 6.0 _MSC_VER == 1200 optional_COMPILER_MSVC_VERSION == 60 (Visual Studio 6.0) +// MSVC++ 7.0 _MSC_VER == 1300 optional_COMPILER_MSVC_VERSION == 70 (Visual Studio .NET 2002) +// MSVC++ 7.1 _MSC_VER == 1310 optional_COMPILER_MSVC_VERSION == 71 (Visual Studio .NET 2003) +// MSVC++ 8.0 _MSC_VER == 1400 optional_COMPILER_MSVC_VERSION == 80 (Visual Studio 2005) +// MSVC++ 9.0 _MSC_VER == 1500 optional_COMPILER_MSVC_VERSION == 90 (Visual Studio 2008) +// MSVC++ 10.0 _MSC_VER == 1600 optional_COMPILER_MSVC_VERSION == 100 (Visual Studio 2010) +// MSVC++ 11.0 _MSC_VER == 1700 optional_COMPILER_MSVC_VERSION == 110 (Visual Studio 2012) +// MSVC++ 12.0 _MSC_VER == 1800 optional_COMPILER_MSVC_VERSION == 120 (Visual Studio 2013) +// MSVC++ 14.0 _MSC_VER == 1900 optional_COMPILER_MSVC_VERSION == 140 (Visual Studio 2015) +// MSVC++ 14.1 _MSC_VER >= 1910 optional_COMPILER_MSVC_VERSION == 141 (Visual Studio 2017) +// MSVC++ 14.2 _MSC_VER >= 1920 optional_COMPILER_MSVC_VERSION == 142 (Visual Studio 2019) + +#if defined(_MSC_VER ) && !defined(__clang__) +# define optional_COMPILER_MSVC_VER (_MSC_VER ) +# define optional_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) ) +#else +# define optional_COMPILER_MSVC_VER 0 +# define optional_COMPILER_MSVC_VERSION 0 +#endif + +#define optional_COMPILER_VERSION( major, minor, patch ) ( 10 * (10 * (major) + (minor) ) + (patch) ) + +#if defined(__GNUC__) && !defined(__clang__) +# define optional_COMPILER_GNUC_VERSION optional_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#else +# define optional_COMPILER_GNUC_VERSION 0 +#endif + +#if defined(__clang__) +# define optional_COMPILER_CLANG_VERSION optional_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) +#else +# define optional_COMPILER_CLANG_VERSION 0 +#endif + +#if optional_BETWEEN(optional_COMPILER_MSVC_VERSION, 70, 140 ) +# pragma warning( disable: 4345 ) // initialization behavior changed +#endif + +#if optional_BETWEEN(optional_COMPILER_MSVC_VERSION, 70, 150 ) +# pragma warning( disable: 4814 ) // in C++14 'constexpr' will not imply 'const' +#endif + +// Presence of language and library features: + +#define optional_HAVE(FEATURE) ( optional_HAVE_##FEATURE ) + +#ifdef _HAS_CPP0X +# define optional_HAS_CPP0X _HAS_CPP0X +#else +# define optional_HAS_CPP0X 0 +#endif + +// Unless defined otherwise below, consider VC14 as C++11 for optional-lite: + +#if optional_COMPILER_MSVC_VER >= 1900 +# undef optional_CPP11_OR_GREATER +# define optional_CPP11_OR_GREATER 1 +#endif + +#define optional_CPP11_90 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1500) +#define optional_CPP11_100 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1600) +#define optional_CPP11_110 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1700) +#define optional_CPP11_120 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1800) +#define optional_CPP11_140 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1900) +#define optional_CPP11_141 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1910) + +#define optional_CPP14_000 (optional_CPP14_OR_GREATER) +#define optional_CPP17_000 (optional_CPP17_OR_GREATER) + +// gcc >= 4.9, msvc >= vc14.1 (vs17): +#define optional_CPP11_140_G490 ((optional_CPP11_OR_GREATER_ && optional_COMPILER_GNUC_VERSION >= 490) || (optional_COMPILER_MSVC_VER >= 1910)) + +// clang >= 3.5, msvc >= vc11 (vs12): +#define optional_CPP11_110_C350 ( optional_CPP11_110 && !optional_BETWEEN( optional_COMPILER_CLANG_VERSION, 1, 350 ) ) + +// clang >= 3.5, gcc >= 5.0, msvc >= vc11 (vs12): +#define optional_CPP11_110_C350_G500 \ + ( optional_CPP11_110 && \ + !( optional_BETWEEN( optional_COMPILER_CLANG_VERSION, 1, 350 ) \ + || optional_BETWEEN( optional_COMPILER_GNUC_VERSION , 1, 500 ) ) ) + +// Presence of C++11 language features: + +#define optional_HAVE_CONSTEXPR_11 optional_CPP11_140 +#define optional_HAVE_IS_DEFAULT optional_CPP11_140 +#define optional_HAVE_NOEXCEPT optional_CPP11_140 +#define optional_HAVE_NULLPTR optional_CPP11_100 +#define optional_HAVE_REF_QUALIFIER optional_CPP11_140_G490 +#define optional_HAVE_INITIALIZER_LIST optional_CPP11_140 + +// Presence of C++14 language features: + +#define optional_HAVE_CONSTEXPR_14 optional_CPP14_000 + +// Presence of C++17 language features: + +#define optional_HAVE_NODISCARD optional_CPP17_000 + +// Presence of C++ library features: + +#define optional_HAVE_CONDITIONAL optional_CPP11_120 +#define optional_HAVE_REMOVE_CV optional_CPP11_120 +#define optional_HAVE_TYPE_TRAITS optional_CPP11_90 + +#define optional_HAVE_TR1_TYPE_TRAITS (!! optional_COMPILER_GNUC_VERSION ) +#define optional_HAVE_TR1_ADD_POINTER (!! optional_COMPILER_GNUC_VERSION ) + +#define optional_HAVE_IS_ASSIGNABLE optional_CPP11_110_C350 +#define optional_HAVE_IS_MOVE_CONSTRUCTIBLE optional_CPP11_110_C350 +#define optional_HAVE_IS_NOTHROW_MOVE_ASSIGNABLE optional_CPP11_110_C350 +#define optional_HAVE_IS_NOTHROW_MOVE_CONSTRUCTIBLE optional_CPP11_110_C350 +#define optional_HAVE_IS_TRIVIALLY_COPY_CONSTRUCTIBLE optional_CPP11_110_C350_G500 +#define optional_HAVE_IS_TRIVIALLY_MOVE_CONSTRUCTIBLE optional_CPP11_110_C350_G500 + +// C++ feature usage: + +#if optional_HAVE( CONSTEXPR_11 ) +# define optional_constexpr constexpr +#else +# define optional_constexpr /*constexpr*/ +#endif + +#if optional_HAVE( IS_DEFAULT ) +# define optional_is_default = default; +#else +# define optional_is_default {} +#endif + +#if optional_HAVE( CONSTEXPR_14 ) +# define optional_constexpr14 constexpr +#else +# define optional_constexpr14 /*constexpr*/ +#endif + +#if optional_HAVE( NODISCARD ) +# define optional_nodiscard [[nodiscard]] +#else +# define optional_nodiscard /*[[nodiscard]]*/ +#endif + +#if optional_HAVE( NOEXCEPT ) +# define optional_noexcept noexcept +#else +# define optional_noexcept /*noexcept*/ +#endif + +#if optional_HAVE( NULLPTR ) +# define optional_nullptr nullptr +#else +# define optional_nullptr NULL +#endif + +#if optional_HAVE( REF_QUALIFIER ) +// NOLINTNEXTLINE( bugprone-macro-parentheses ) +# define optional_ref_qual & +# define optional_refref_qual && +#else +# define optional_ref_qual /*&*/ +# define optional_refref_qual /*&&*/ +#endif + +// additional includes: + +#if optional_CONFIG_NO_EXCEPTIONS +// already included: +#else +# include +#endif + +#if optional_CPP11_OR_GREATER +# include +#endif + +#if optional_HAVE( INITIALIZER_LIST ) +# include +#endif + +#if optional_HAVE( TYPE_TRAITS ) +# include +#elif optional_HAVE( TR1_TYPE_TRAITS ) +# include +#endif + +// Method enabling + +#if optional_CPP11_OR_GREATER + +#define optional_REQUIRES_0(...) \ + template< bool B = (__VA_ARGS__), typename std::enable_if::type = 0 > + +#define optional_REQUIRES_T(...) \ + , typename std::enable_if< (__VA_ARGS__), int >::type = 0 + +#define optional_REQUIRES_R(R, ...) \ + typename std::enable_if< (__VA_ARGS__), R>::type + +#define optional_REQUIRES_A(...) \ + , typename std::enable_if< (__VA_ARGS__), void*>::type = nullptr + +#endif + +// +// optional: +// + +namespace nonstd { namespace optional_lite { + +namespace std11 { + +template< class T, T v > struct integral_constant { enum { value = v }; }; +template< bool B > struct bool_constant : integral_constant{}; + +typedef bool_constant< true > true_type; +typedef bool_constant< false > false_type; + +#if optional_CPP11_OR_GREATER + using std::move; +#else + template< typename T > T & move( T & t ) { return t; } +#endif + +#if optional_HAVE( CONDITIONAL ) + using std::conditional; +#else + template< bool B, typename T, typename F > struct conditional { typedef T type; }; + template< typename T, typename F > struct conditional { typedef F type; }; +#endif // optional_HAVE_CONDITIONAL + +#if optional_HAVE( IS_ASSIGNABLE ) + using std::is_assignable; +#else + template< class T, class U > struct is_assignable : std11::true_type{}; +#endif + +#if optional_HAVE( IS_MOVE_CONSTRUCTIBLE ) + using std::is_move_constructible; +#else + template< class T > struct is_move_constructible : std11::true_type{}; +#endif + +#if optional_HAVE( IS_NOTHROW_MOVE_ASSIGNABLE ) + using std::is_nothrow_move_assignable; +#else + template< class T > struct is_nothrow_move_assignable : std11::true_type{}; +#endif + +#if optional_HAVE( IS_NOTHROW_MOVE_CONSTRUCTIBLE ) + using std::is_nothrow_move_constructible; +#else + template< class T > struct is_nothrow_move_constructible : std11::true_type{}; +#endif + +#if optional_HAVE( IS_TRIVIALLY_COPY_CONSTRUCTIBLE ) + using std::is_trivially_copy_constructible; +#else + template< class T > struct is_trivially_copy_constructible : std11::true_type{}; +#endif + +#if optional_HAVE( IS_TRIVIALLY_MOVE_CONSTRUCTIBLE ) + using std::is_trivially_move_constructible; +#else + template< class T > struct is_trivially_move_constructible : std11::true_type{}; +#endif + +} // namespace std11 + +#if optional_CPP11_OR_GREATER + +/// type traits C++17: + +namespace std17 { + +#if optional_CPP17_OR_GREATER + +using std::is_swappable; +using std::is_nothrow_swappable; + +#elif optional_CPP11_OR_GREATER + +namespace detail { + +using std::swap; + +struct is_swappable +{ + template< typename T, typename = decltype( swap( std::declval(), std::declval() ) ) > + static std11::true_type test( int /*unused*/ ); + + template< typename > + static std11::false_type test(...); +}; + +struct is_nothrow_swappable +{ + // wrap noexcept(expr) in separate function as work-around for VC140 (VS2015): + + template< typename T > + static constexpr bool satisfies() + { + return noexcept( swap( std::declval(), std::declval() ) ); + } + + template< typename T > + static auto test( int /*unused*/ ) -> std11::integral_constant()>{} + + template< typename > + static auto test(...) -> std11::false_type; +}; + +} // namespace detail + +// is [nothow] swappable: + +template< typename T > +struct is_swappable : decltype( detail::is_swappable::test(0) ){}; + +template< typename T > +struct is_nothrow_swappable : decltype( detail::is_nothrow_swappable::test(0) ){}; + +#endif // optional_CPP17_OR_GREATER + +} // namespace std17 + +/// type traits C++20: + +namespace std20 { + +template< typename T > +struct remove_cvref +{ + typedef typename std::remove_cv< typename std::remove_reference::type >::type type; +}; + +} // namespace std20 + +#endif // optional_CPP11_OR_GREATER + +/// class optional + +template< typename T > +class optional; + +namespace detail { + +// C++11 emulation: + +struct nulltype{}; + +template< typename Head, typename Tail > +struct typelist +{ + typedef Head head; + typedef Tail tail; +}; + +#if optional_CONFIG_MAX_ALIGN_HACK + +// Max align, use most restricted type for alignment: + +#define optional_UNIQUE( name ) optional_UNIQUE2( name, __LINE__ ) +#define optional_UNIQUE2( name, line ) optional_UNIQUE3( name, line ) +#define optional_UNIQUE3( name, line ) name ## line + +#define optional_ALIGN_TYPE( type ) \ + type optional_UNIQUE( _t ); struct_t< type > optional_UNIQUE( _st ) + +template< typename T > +struct struct_t { T _; }; + +union max_align_t +{ + optional_ALIGN_TYPE( char ); + optional_ALIGN_TYPE( short int ); + optional_ALIGN_TYPE( int ); + optional_ALIGN_TYPE( long int ); + optional_ALIGN_TYPE( float ); + optional_ALIGN_TYPE( double ); + optional_ALIGN_TYPE( long double ); + optional_ALIGN_TYPE( char * ); + optional_ALIGN_TYPE( short int * ); + optional_ALIGN_TYPE( int * ); + optional_ALIGN_TYPE( long int * ); + optional_ALIGN_TYPE( float * ); + optional_ALIGN_TYPE( double * ); + optional_ALIGN_TYPE( long double * ); + optional_ALIGN_TYPE( void * ); + +#ifdef HAVE_LONG_LONG + optional_ALIGN_TYPE( long long ); +#endif + + struct Unknown; + + Unknown ( * optional_UNIQUE(_) )( Unknown ); + Unknown * Unknown::* optional_UNIQUE(_); + Unknown ( Unknown::* optional_UNIQUE(_) )( Unknown ); + + struct_t< Unknown ( * )( Unknown) > optional_UNIQUE(_); + struct_t< Unknown * Unknown::* > optional_UNIQUE(_); + struct_t< Unknown ( Unknown::* )(Unknown) > optional_UNIQUE(_); +}; + +#undef optional_UNIQUE +#undef optional_UNIQUE2 +#undef optional_UNIQUE3 + +#undef optional_ALIGN_TYPE + +#elif defined( optional_CONFIG_ALIGN_AS ) // optional_CONFIG_MAX_ALIGN_HACK + +// Use user-specified type for alignment: + +#define optional_ALIGN_AS( unused ) \ + optional_CONFIG_ALIGN_AS + +#else // optional_CONFIG_MAX_ALIGN_HACK + +// Determine POD type to use for alignment: + +#define optional_ALIGN_AS( to_align ) \ + typename type_of_size< alignment_types, alignment_of< to_align >::value >::type + +template< typename T > +struct alignment_of; + +template< typename T > +struct alignment_of_hack +{ + char c; + T t; + alignment_of_hack(); +}; + +template< size_t A, size_t S > +struct alignment_logic +{ + enum { value = A < S ? A : S }; +}; + +template< typename T > +struct alignment_of +{ + enum { value = alignment_logic< + sizeof( alignment_of_hack ) - sizeof(T), sizeof(T) >::value }; +}; + +template< typename List, size_t N > +struct type_of_size +{ + typedef typename std11::conditional< + N == sizeof( typename List::head ), + typename List::head, + typename type_of_size::type >::type type; +}; + +template< size_t N > +struct type_of_size< nulltype, N > +{ + typedef optional_CONFIG_ALIGN_AS_FALLBACK type; +}; + +template< typename T> +struct struct_t { T _; }; + +#define optional_ALIGN_TYPE( type ) \ + typelist< type , typelist< struct_t< type > + +struct Unknown; + +typedef + optional_ALIGN_TYPE( char ), + optional_ALIGN_TYPE( short ), + optional_ALIGN_TYPE( int ), + optional_ALIGN_TYPE( long ), + optional_ALIGN_TYPE( float ), + optional_ALIGN_TYPE( double ), + optional_ALIGN_TYPE( long double ), + + optional_ALIGN_TYPE( char *), + optional_ALIGN_TYPE( short * ), + optional_ALIGN_TYPE( int * ), + optional_ALIGN_TYPE( long * ), + optional_ALIGN_TYPE( float * ), + optional_ALIGN_TYPE( double * ), + optional_ALIGN_TYPE( long double * ), + + optional_ALIGN_TYPE( Unknown ( * )( Unknown ) ), + optional_ALIGN_TYPE( Unknown * Unknown::* ), + optional_ALIGN_TYPE( Unknown ( Unknown::* )( Unknown ) ), + + nulltype + > > > > > > > > > > > > > > + > > > > > > > > > > > > > > + > > > > > > + alignment_types; + +#undef optional_ALIGN_TYPE + +#endif // optional_CONFIG_MAX_ALIGN_HACK + +/// C++03 constructed union to hold value. + +template< typename T > +union storage_t +{ +//private: +// template< typename > friend class optional; + + typedef T value_type; + + storage_t() optional_is_default + + explicit storage_t( value_type const & v ) + { + construct_value( v ); + } + + void construct_value( value_type const & v ) + { + ::new( value_ptr() ) value_type( v ); + } + +#if optional_CPP11_OR_GREATER + + explicit storage_t( value_type && v ) + { + construct_value( std::move( v ) ); + } + + void construct_value( value_type && v ) + { + ::new( value_ptr() ) value_type( std::move( v ) ); + } + + template< class... Args > + void emplace( Args&&... args ) + { + ::new( value_ptr() ) value_type( std::forward(args)... ); + } + + template< class U, class... Args > + void emplace( std::initializer_list il, Args&&... args ) + { + ::new( value_ptr() ) value_type( il, std::forward(args)... ); + } + +#endif + + void destruct_value() + { + value_ptr()->~T(); + } + + optional_nodiscard value_type const * value_ptr() const + { + return as(); + } + + value_type * value_ptr() + { + return as(); + } + + optional_nodiscard value_type const & value() const optional_ref_qual + { + return * value_ptr(); + } + + value_type & value() optional_ref_qual + { + return * value_ptr(); + } + +#if optional_HAVE( REF_QUALIFIER ) + + optional_nodiscard value_type const && value() const optional_refref_qual + { + return std::move( value() ); + } + + value_type && value() optional_refref_qual + { + return std::move( value() ); + } + +#endif + +#if optional_CPP11_OR_GREATER + + using aligned_storage_t = typename std::aligned_storage< sizeof(value_type), alignof(value_type) >::type; + aligned_storage_t data; + +#elif optional_CONFIG_MAX_ALIGN_HACK + + typedef struct { unsigned char data[ sizeof(value_type) ]; } aligned_storage_t; + + max_align_t hack; + aligned_storage_t data; + +#else + typedef optional_ALIGN_AS(value_type) align_as_type; + + typedef struct { align_as_type data[ 1 + ( sizeof(value_type) - 1 ) / sizeof(align_as_type) ]; } aligned_storage_t; + aligned_storage_t data; + +# undef optional_ALIGN_AS + +#endif // optional_CONFIG_MAX_ALIGN_HACK + + optional_nodiscard void * ptr() optional_noexcept + { + return &data; + } + + optional_nodiscard void const * ptr() const optional_noexcept + { + return &data; + } + + template + optional_nodiscard U * as() + { + return reinterpret_cast( ptr() ); + } + + template + optional_nodiscard U const * as() const + { + return reinterpret_cast( ptr() ); + } +}; + +} // namespace detail + +/// disengaged state tag + +struct nullopt_t +{ + struct init{}; + explicit optional_constexpr nullopt_t( init /*unused*/ ) optional_noexcept {} +}; + +#if optional_HAVE( CONSTEXPR_11 ) +constexpr nullopt_t nullopt{ nullopt_t::init{} }; +#else +// extra parenthesis to prevent the most vexing parse: +const nullopt_t nullopt(( nullopt_t::init() )); +#endif + +/// optional access error + +#if ! optional_CONFIG_NO_EXCEPTIONS + +class bad_optional_access : public std::logic_error +{ +public: + explicit bad_optional_access() + : logic_error( "bad optional access" ) {} +}; + +#endif //optional_CONFIG_NO_EXCEPTIONS + +/// optional + +template< typename T> +class optional +{ +private: + template< typename > friend class optional; + + typedef void (optional::*safe_bool)() const; + +public: + typedef T value_type; + + // x.x.3.1, constructors + + // 1a - default construct + optional_constexpr optional() optional_noexcept + : has_value_( false ) + , contained() + {} + + // 1b - construct explicitly empty + // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions ) + optional_constexpr optional( nullopt_t /*unused*/ ) optional_noexcept + : has_value_( false ) + , contained() + {} + + // 2 - copy-construct +#if optional_CPP11_OR_GREATER + // template< typename U = T + // optional_REQUIRES_T( + // std::is_copy_constructible::value + // || std11::is_trivially_copy_constructible::value + // ) + // > +#endif + optional_constexpr14 optional( optional const & other ) + : has_value_( other.has_value() ) + { + if ( other.has_value() ) + { + contained.construct_value( other.contained.value() ); + } + } + +#if optional_CPP11_OR_GREATER + + // 3 (C++11) - move-construct from optional + template< typename U = T + optional_REQUIRES_T( + std11::is_move_constructible::value + || std11::is_trivially_move_constructible::value + ) + > + optional_constexpr14 optional( optional && other ) + // NOLINTNEXTLINE( performance-noexcept-move-constructor ) + noexcept( std11::is_nothrow_move_constructible::value ) + : has_value_( other.has_value() ) + { + if ( other.has_value() ) + { + contained.construct_value( std::move( other.contained.value() ) ); + } + } + + // 4a (C++11) - explicit converting copy-construct from optional + template< typename U + optional_REQUIRES_T( + std::is_constructible::value + && !std::is_constructible & >::value + && !std::is_constructible && >::value + && !std::is_constructible const & >::value + && !std::is_constructible const && >::value + && !std::is_convertible< optional & , T>::value + && !std::is_convertible< optional && , T>::value + && !std::is_convertible< optional const & , T>::value + && !std::is_convertible< optional const &&, T>::value + && !std::is_convertible< U const & , T>::value /*=> explicit */ + ) + > + explicit optional( optional const & other ) + : has_value_( other.has_value() ) + { + if ( other.has_value() ) + { + contained.construct_value( T{ other.contained.value() } ); + } + } +#endif // optional_CPP11_OR_GREATER + + // 4b (C++98 and later) - non-explicit converting copy-construct from optional + template< typename U +#if optional_CPP11_OR_GREATER + optional_REQUIRES_T( + std::is_constructible::value + && !std::is_constructible & >::value + && !std::is_constructible && >::value + && !std::is_constructible const & >::value + && !std::is_constructible const && >::value + && !std::is_convertible< optional & , T>::value + && !std::is_convertible< optional && , T>::value + && !std::is_convertible< optional const & , T>::value + && !std::is_convertible< optional const &&, T>::value + && std::is_convertible< U const & , T>::value /*=> non-explicit */ + ) +#endif // optional_CPP11_OR_GREATER + > + // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions ) + /*non-explicit*/ optional( optional const & other ) + : has_value_( other.has_value() ) + { + if ( other.has_value() ) + { + contained.construct_value( other.contained.value() ); + } + } + +#if optional_CPP11_OR_GREATER + + // 5a (C++11) - explicit converting move-construct from optional + template< typename U + optional_REQUIRES_T( + std::is_constructible::value + && !std::is_constructible & >::value + && !std::is_constructible && >::value + && !std::is_constructible const & >::value + && !std::is_constructible const && >::value + && !std::is_convertible< optional & , T>::value + && !std::is_convertible< optional && , T>::value + && !std::is_convertible< optional const & , T>::value + && !std::is_convertible< optional const &&, T>::value + && !std::is_convertible< U &&, T>::value /*=> explicit */ + ) + > + explicit optional( optional && other + ) + : has_value_( other.has_value() ) + { + if ( other.has_value() ) + { + contained.construct_value( T{ std::move( other.contained.value() ) } ); + } + } + + // 5a (C++11) - non-explicit converting move-construct from optional + template< typename U + optional_REQUIRES_T( + std::is_constructible::value + && !std::is_constructible & >::value + && !std::is_constructible && >::value + && !std::is_constructible const & >::value + && !std::is_constructible const && >::value + && !std::is_convertible< optional & , T>::value + && !std::is_convertible< optional && , T>::value + && !std::is_convertible< optional const & , T>::value + && !std::is_convertible< optional const &&, T>::value + && std::is_convertible< U &&, T>::value /*=> non-explicit */ + ) + > + // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions ) + /*non-explicit*/ optional( optional && other ) + : has_value_( other.has_value() ) + { + if ( other.has_value() ) + { + contained.construct_value( std::move( other.contained.value() ) ); + } + } + + // 6 (C++11) - in-place construct + template< typename... Args + optional_REQUIRES_T( + std::is_constructible::value + ) + > + optional_constexpr explicit optional( nonstd_lite_in_place_t(T), Args&&... args ) + : has_value_( true ) + , contained( T( std::forward(args)...) ) + {} + + // 7 (C++11) - in-place construct, initializer-list + template< typename U, typename... Args + optional_REQUIRES_T( + std::is_constructible&, Args&&...>::value + ) + > + optional_constexpr explicit optional( nonstd_lite_in_place_t(T), std::initializer_list il, Args&&... args ) + : has_value_( true ) + , contained( T( il, std::forward(args)...) ) + {} + + // 8a (C++11) - explicit move construct from value + template< typename U = T + optional_REQUIRES_T( + std::is_constructible::value + && !std::is_same::type, nonstd_lite_in_place_t(U)>::value + && !std::is_same::type, optional>::value + && !std::is_convertible::value /*=> explicit */ + ) + > + optional_constexpr explicit optional( U && value ) + : has_value_( true ) + , contained( T{ std::forward( value ) } ) + {} + + // 8b (C++11) - non-explicit move construct from value + template< typename U = T + optional_REQUIRES_T( + std::is_constructible::value + && !std::is_same::type, nonstd_lite_in_place_t(U)>::value + && !std::is_same::type, optional>::value + && std::is_convertible::value /*=> non-explicit */ + ) + > + // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions ) + optional_constexpr /*non-explicit*/ optional( U && value ) + : has_value_( true ) + , contained( std::forward( value ) ) + {} + +#else // optional_CPP11_OR_GREATER + + // 8 (C++98) + optional( value_type const & value ) + : has_value_( true ) + , contained( value ) + {} + +#endif // optional_CPP11_OR_GREATER + + // x.x.3.2, destructor + + ~optional() + { + if ( has_value() ) + { + contained.destruct_value(); + } + } + + // x.x.3.3, assignment + + // 1 (C++98and later) - assign explicitly empty + optional & operator=( nullopt_t /*unused*/) optional_noexcept + { + reset(); + return *this; + } + + // 2 (C++98and later) - copy-assign from optional +#if optional_CPP11_OR_GREATER + // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) + optional_REQUIRES_R( + optional &, + true +// std::is_copy_constructible::value +// && std::is_copy_assignable::value + ) + operator=( optional const & other ) + noexcept( + std11::is_nothrow_move_assignable::value + && std11::is_nothrow_move_constructible::value + ) +#else + optional & operator=( optional const & other ) +#endif + { + if ( (has_value() == true ) && (other.has_value() == false) ) { reset(); } + else if ( (has_value() == false) && (other.has_value() == true ) ) { initialize( *other ); } + else if ( (has_value() == true ) && (other.has_value() == true ) ) { contained.value() = *other; } + return *this; + } + +#if optional_CPP11_OR_GREATER + + // 3 (C++11) - move-assign from optional + // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) + optional_REQUIRES_R( + optional &, + true +// std11::is_move_constructible::value +// && std::is_move_assignable::value + ) + operator=( optional && other ) noexcept + { + if ( (has_value() == true ) && (other.has_value() == false) ) { reset(); } + else if ( (has_value() == false) && (other.has_value() == true ) ) { initialize( std::move( *other ) ); } + else if ( (has_value() == true ) && (other.has_value() == true ) ) { contained.value() = std::move( *other ); } + return *this; + } + + // 4 (C++11) - move-assign from value + template< typename U = T > + // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) + optional_REQUIRES_R( + optional &, + std::is_constructible::value + && std11::is_assignable::value + && !std::is_same::type, nonstd_lite_in_place_t(U)>::value + && !std::is_same::type, optional>::value + && !(std::is_scalar::value && std::is_same::type>::value) + ) + operator=( U && value ) + { + if ( has_value() ) + { + contained.value() = std::forward( value ); + } + else + { + initialize( T( std::forward( value ) ) ); + } + return *this; + } + +#else // optional_CPP11_OR_GREATER + + // 4 (C++98) - copy-assign from value + template< typename U /*= T*/ > + optional & operator=( U const & value ) + { + if ( has_value() ) contained.value() = value; + else initialize( T( value ) ); + return *this; + } + +#endif // optional_CPP11_OR_GREATER + + // 5 (C++98 and later) - converting copy-assign from optional + template< typename U > +#if optional_CPP11_OR_GREATER + // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) + optional_REQUIRES_R( + optional&, + std::is_constructible< T , U const &>::value + && std11::is_assignable< T&, U const &>::value + && !std::is_constructible & >::value + && !std::is_constructible && >::value + && !std::is_constructible const & >::value + && !std::is_constructible const && >::value + && !std::is_convertible< optional & , T>::value + && !std::is_convertible< optional && , T>::value + && !std::is_convertible< optional const & , T>::value + && !std::is_convertible< optional const &&, T>::value + && !std11::is_assignable< T&, optional & >::value + && !std11::is_assignable< T&, optional && >::value + && !std11::is_assignable< T&, optional const & >::value + && !std11::is_assignable< T&, optional const && >::value + ) +#else + optional& +#endif // optional_CPP11_OR_GREATER + operator=( optional const & other ) + { + return *this = optional( other ); + } + +#if optional_CPP11_OR_GREATER + + // 6 (C++11) - converting move-assign from optional + template< typename U > + // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) + optional_REQUIRES_R( + optional&, + std::is_constructible< T , U>::value + && std11::is_assignable< T&, U>::value + && !std::is_constructible & >::value + && !std::is_constructible && >::value + && !std::is_constructible const & >::value + && !std::is_constructible const && >::value + && !std::is_convertible< optional & , T>::value + && !std::is_convertible< optional && , T>::value + && !std::is_convertible< optional const & , T>::value + && !std::is_convertible< optional const &&, T>::value + && !std11::is_assignable< T&, optional & >::value + && !std11::is_assignable< T&, optional && >::value + && !std11::is_assignable< T&, optional const & >::value + && !std11::is_assignable< T&, optional const && >::value + ) + operator=( optional && other ) + { + return *this = optional( std::move( other ) ); + } + + // 7 (C++11) - emplace + template< typename... Args + optional_REQUIRES_T( + std::is_constructible::value + ) + > + T& emplace( Args&&... args ) + { + *this = nullopt; + contained.emplace( std::forward(args)... ); + has_value_ = true; + return contained.value(); + } + + // 8 (C++11) - emplace, initializer-list + template< typename U, typename... Args + optional_REQUIRES_T( + std::is_constructible&, Args&&...>::value + ) + > + T& emplace( std::initializer_list il, Args&&... args ) + { + *this = nullopt; + contained.emplace( il, std::forward(args)... ); + has_value_ = true; + return contained.value(); + } + +#endif // optional_CPP11_OR_GREATER + + // x.x.3.4, swap + + void swap( optional & other ) +#if optional_CPP11_OR_GREATER + noexcept( + std11::is_nothrow_move_constructible::value + && std17::is_nothrow_swappable::value + ) +#endif + { + using std::swap; + if ( (has_value() == true ) && (other.has_value() == true ) ) { swap( **this, *other ); } + else if ( (has_value() == false) && (other.has_value() == true ) ) { initialize( std11::move(*other) ); other.reset(); } + else if ( (has_value() == true ) && (other.has_value() == false) ) { other.initialize( std11::move(**this) ); reset(); } + } + + // x.x.3.5, observers + + optional_constexpr value_type const * operator ->() const + { + return assert( has_value() ), + contained.value_ptr(); + } + + optional_constexpr14 value_type * operator ->() + { + return assert( has_value() ), + contained.value_ptr(); + } + + optional_constexpr value_type const & operator *() const optional_ref_qual + { + return assert( has_value() ), + contained.value(); + } + + optional_constexpr14 value_type & operator *() optional_ref_qual + { + return assert( has_value() ), + contained.value(); + } + +#if optional_HAVE( REF_QUALIFIER ) + + optional_constexpr value_type const && operator *() const optional_refref_qual + { + return std::move( **this ); + } + + optional_constexpr14 value_type && operator *() optional_refref_qual + { + return std::move( **this ); + } + +#endif + +#if optional_CPP11_OR_GREATER + optional_constexpr explicit operator bool() const optional_noexcept + { + return has_value(); + } +#else + optional_constexpr operator safe_bool() const optional_noexcept + { + return has_value() ? &optional::this_type_does_not_support_comparisons : 0; + } +#endif + + // NOLINTNEXTLINE( modernize-use-nodiscard ) + /*optional_nodiscard*/ optional_constexpr bool has_value() const optional_noexcept + { + return has_value_; + } + + // NOLINTNEXTLINE( modernize-use-nodiscard ) + /*optional_nodiscard*/ optional_constexpr14 value_type const & value() const optional_ref_qual + { +#if optional_CONFIG_NO_EXCEPTIONS + assert( has_value() ); +#else + if ( ! has_value() ) + { + throw bad_optional_access(); + } +#endif + return contained.value(); + } + + optional_constexpr14 value_type & value() optional_ref_qual + { +#if optional_CONFIG_NO_EXCEPTIONS + assert( has_value() ); +#else + if ( ! has_value() ) + { + throw bad_optional_access(); + } +#endif + return contained.value(); + } + +#if optional_HAVE( REF_QUALIFIER ) && ( !optional_COMPILER_GNUC_VERSION || optional_COMPILER_GNUC_VERSION >= 490 ) + + // NOLINTNEXTLINE( modernize-use-nodiscard ) + /*optional_nodiscard*/ optional_constexpr value_type const && value() const optional_refref_qual + { + return std::move( value() ); + } + + optional_constexpr14 value_type && value() optional_refref_qual + { + return std::move( value() ); + } + +#endif + +#if optional_CPP11_OR_GREATER + + template< typename U > + optional_constexpr value_type value_or( U && v ) const optional_ref_qual + { + return has_value() ? contained.value() : static_cast(std::forward( v ) ); + } + + template< typename U > + optional_constexpr14 value_type value_or( U && v ) optional_refref_qual + { + return has_value() ? std::move( contained.value() ) : static_cast(std::forward( v ) ); + } + +#else + + template< typename U > + optional_constexpr value_type value_or( U const & v ) const + { + return has_value() ? contained.value() : static_cast( v ); + } + +#endif // optional_CPP11_OR_GREATER + + // x.x.3.6, modifiers + + void reset() optional_noexcept + { + if ( has_value() ) + { + contained.destruct_value(); + } + + has_value_ = false; + } + +private: + void this_type_does_not_support_comparisons() const {} + + template< typename V > + void initialize( V const & value ) + { + assert( ! has_value() ); + contained.construct_value( value ); + has_value_ = true; + } + +#if optional_CPP11_OR_GREATER + template< typename V > + void initialize( V && value ) + { + assert( ! has_value() ); + contained.construct_value( std::move( value ) ); + has_value_ = true; + } + +#endif + +private: + bool has_value_; + detail::storage_t< value_type > contained; + +}; + +// Relational operators + +template< typename T, typename U > +inline optional_constexpr bool operator==( optional const & x, optional const & y ) +{ + return bool(x) != bool(y) ? false : !bool( x ) ? true : *x == *y; +} + +template< typename T, typename U > +inline optional_constexpr bool operator!=( optional const & x, optional const & y ) +{ + return !(x == y); +} + +template< typename T, typename U > +inline optional_constexpr bool operator<( optional const & x, optional const & y ) +{ + return (!y) ? false : (!x) ? true : *x < *y; +} + +template< typename T, typename U > +inline optional_constexpr bool operator>( optional const & x, optional const & y ) +{ + return (y < x); +} + +template< typename T, typename U > +inline optional_constexpr bool operator<=( optional const & x, optional const & y ) +{ + return !(y < x); +} + +template< typename T, typename U > +inline optional_constexpr bool operator>=( optional const & x, optional const & y ) +{ + return !(x < y); +} + +// Comparison with nullopt + +template< typename T > +inline optional_constexpr bool operator==( optional const & x, nullopt_t /*unused*/ ) optional_noexcept +{ + return (!x); +} + +template< typename T > +inline optional_constexpr bool operator==( nullopt_t /*unused*/, optional const & x ) optional_noexcept +{ + return (!x); +} + +template< typename T > +inline optional_constexpr bool operator!=( optional const & x, nullopt_t /*unused*/ ) optional_noexcept +{ + return bool(x); +} + +template< typename T > +inline optional_constexpr bool operator!=( nullopt_t /*unused*/, optional const & x ) optional_noexcept +{ + return bool(x); +} + +template< typename T > +inline optional_constexpr bool operator<( optional const & /*unused*/, nullopt_t /*unused*/ ) optional_noexcept +{ + return false; +} + +template< typename T > +inline optional_constexpr bool operator<( nullopt_t /*unused*/, optional const & x ) optional_noexcept +{ + return bool(x); +} + +template< typename T > +inline optional_constexpr bool operator<=( optional const & x, nullopt_t /*unused*/ ) optional_noexcept +{ + return (!x); +} + +template< typename T > +inline optional_constexpr bool operator<=( nullopt_t /*unused*/, optional const & /*unused*/ ) optional_noexcept +{ + return true; +} + +template< typename T > +inline optional_constexpr bool operator>( optional const & x, nullopt_t /*unused*/ ) optional_noexcept +{ + return bool(x); +} + +template< typename T > +inline optional_constexpr bool operator>( nullopt_t /*unused*/, optional const & /*unused*/ ) optional_noexcept +{ + return false; +} + +template< typename T > +inline optional_constexpr bool operator>=( optional const & /*unused*/, nullopt_t /*unused*/ ) optional_noexcept +{ + return true; +} + +template< typename T > +inline optional_constexpr bool operator>=( nullopt_t /*unused*/, optional const & x ) optional_noexcept +{ + return (!x); +} + +// Comparison with T + +template< typename T, typename U > +inline optional_constexpr bool operator==( optional const & x, U const & v ) +{ + return bool(x) ? *x == v : false; +} + +template< typename T, typename U > +inline optional_constexpr bool operator==( U const & v, optional const & x ) +{ + return bool(x) ? v == *x : false; +} + +template< typename T, typename U > +inline optional_constexpr bool operator!=( optional const & x, U const & v ) +{ + return bool(x) ? *x != v : true; +} + +template< typename T, typename U > +inline optional_constexpr bool operator!=( U const & v, optional const & x ) +{ + return bool(x) ? v != *x : true; +} + +template< typename T, typename U > +inline optional_constexpr bool operator<( optional const & x, U const & v ) +{ + return bool(x) ? *x < v : true; +} + +template< typename T, typename U > +inline optional_constexpr bool operator<( U const & v, optional const & x ) +{ + return bool(x) ? v < *x : false; +} + +template< typename T, typename U > +inline optional_constexpr bool operator<=( optional const & x, U const & v ) +{ + return bool(x) ? *x <= v : true; +} + +template< typename T, typename U > +inline optional_constexpr bool operator<=( U const & v, optional const & x ) +{ + return bool(x) ? v <= *x : false; +} + +template< typename T, typename U > +inline optional_constexpr bool operator>( optional const & x, U const & v ) +{ + return bool(x) ? *x > v : false; +} + +template< typename T, typename U > +inline optional_constexpr bool operator>( U const & v, optional const & x ) +{ + return bool(x) ? v > *x : true; +} + +template< typename T, typename U > +inline optional_constexpr bool operator>=( optional const & x, U const & v ) +{ + return bool(x) ? *x >= v : false; +} + +template< typename T, typename U > +inline optional_constexpr bool operator>=( U const & v, optional const & x ) +{ + return bool(x) ? v >= *x : true; +} + +// Specialized algorithms + +template< typename T +#if optional_CPP11_OR_GREATER + optional_REQUIRES_T( + std11::is_move_constructible::value + && std17::is_swappable::value ) +#endif +> +void swap( optional & x, optional & y ) +#if optional_CPP11_OR_GREATER + noexcept( noexcept( x.swap(y) ) ) +#endif +{ + x.swap( y ); +} + +#if optional_CPP11_OR_GREATER + +template< typename T > +optional_constexpr optional< typename std::decay::type > make_optional( T && value ) +{ + return optional< typename std::decay::type >( std::forward( value ) ); +} + +template< typename T, typename...Args > +optional_constexpr optional make_optional( Args&&... args ) +{ + return optional( nonstd_lite_in_place(T), std::forward(args)...); +} + +template< typename T, typename U, typename... Args > +optional_constexpr optional make_optional( std::initializer_list il, Args&&... args ) +{ + return optional( nonstd_lite_in_place(T), il, std::forward(args)...); +} + +#else + +template< typename T > +optional make_optional( T const & value ) +{ + return optional( value ); +} + +#endif // optional_CPP11_OR_GREATER + +} // namespace optional_lite + +using optional_lite::optional; +using optional_lite::nullopt_t; +using optional_lite::nullopt; + +#if ! optional_CONFIG_NO_EXCEPTIONS +using optional_lite::bad_optional_access; +#endif + +using optional_lite::make_optional; + +} // namespace nonstd + +#if optional_CPP11_OR_GREATER + +// specialize the std::hash algorithm: + +namespace std { + +template< class T > +struct hash< nonstd::optional > +{ +public: + std::size_t operator()( nonstd::optional const & v ) const optional_noexcept + { + return bool( v ) ? std::hash{}( *v ) : 0; + } +}; + +} //namespace std + +#endif // optional_CPP11_OR_GREATER + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER ) +# pragma warning( pop ) +#endif + +#endif // optional_USES_STD_OPTIONAL + +#endif // NONSTD_OPTIONAL_LITE_HPP diff --git a/contrib/tinyusdz/tinyusdz_repo/src/path-util.cc b/contrib/tinyusdz/tinyusdz_repo/src/path-util.cc new file mode 100644 index 000000000..5bab87bff --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/path-util.cc @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2023-Present Light Transport Entertainment, Inc. + +#include "path-util.hh" +#include "str-util.hh" +#include "prim-types.hh" +#include "common-macros.inc" + +namespace tinyusdz { +namespace pathutil { + +namespace { + +// Remove sequential "../" +// Returns the number of "../" occurence to `num` +std::string RemoveRelativePrefix(const std::string &in_str, size_t &num) { + constexpr size_t maxnum = 1024*1024; + + num = 0; + std::string ret = in_str; + while (!ret.empty() || (num < maxnum)) { + if (startsWith(ret, "../")) { + ret = removePrefix(ret, "../"); + num++; + } else { + break; + } + } + + return ret; +} + +} // namespace + +Path FromString(const std::string &_path_str) { + + std::string path_str = _path_str; + + if (path_str.empty()) { + return Path(); + } + + if (path_str == ".") { + // invalid + return Path(); + } + + size_t loc = path_str.find_last_of("."); + if (loc != std::string::npos) { + if (loc == (path_str.size() - 1)) { + // ends with "." + return Path(); + } else if ((path_str.size() > 1) && (loc < (path_str.size() - 1))) { + if (path_str[loc+1] == '/') { + // guess relative prim path only. + return Path(path_str, ""); + } + } + } + + if (loc == std::string::npos) { + // prim_part only + return Path(path_str, ""); + } + + std::string prim_part = path_str.substr(0, loc); + std::string prop_part = path_str.substr(loc+1); + + return Path(prim_part, prop_part); +} + +bool ResolveRelativePath(const Path &base_prim_path, const Path &relative_path, Path *abs_path, std::string *err) { + + if (!abs_path) { + return false; + } + + std::string relative_str = relative_path.prim_part(); + std::string base_str = base_prim_path.prim_part(); + + // base_prim_path must be absolute. + if (startsWith(base_str, "/")) { + // ok + } else { + if (err) { + (*err) += "Base Prim path is not absolute path.\n"; + } + return false; + } + + std::string abs_dir; + + if (startsWith(relative_str, "./")) { + // pxrUSD doesn't allow "./", so do same in tinyusdz. +#if 1 + if (err) { + (*err) += "Path starting with `./` is not allowed.\n"; + } + return false; +#else + std::string remainder = removePrefix(relative_str, "./"); + + // "./../", "././", etc is not allowed at the moment. + if (contains_str(remainder, ".")) { + return false; + } + + abs_dir = base_str + "/" + remainder; +#endif + + } else if (startsWith(relative_str, "../")) { + // ok + size_t ndepth{0}; + std::string remainder = RemoveRelativePrefix(relative_str, ndepth); + DCOUT("remainder = " << remainder << ", ndepth = " << ndepth); + + // "../" in subsequent position(e.g. `../bora/../dora`) is not allowed at the moment. + if (contains_str(remainder, ".")) { + if (err) { + (*err) += "`../` in the middle of Path is not allowed.\n"; + } + return false; + } + + std::vector base_dirs = split(base_str, "/"); + DCOUT("base_dirs.len = " << base_dirs.size()); + //if (base_dirs.size() < ndepth) { + // return false; + //} + + if (base_dirs.size() == 0) { // "/" + abs_dir = "/" + remainder; + } else { + int64_t n = int64_t(base_dirs.size()) - int64_t(ndepth); + +#if 1 + // pxrUSD behavior + if (n < -1) { + if (err) { + (*err) += "The number of `../` exceeds Prim path depth.\n"; + } + return false; + } +#else + // Unixish path behavior +#endif + if (n <= 0) { + abs_dir += "/" + remainder; + } else { + for (size_t i = 0; i < size_t(n); i++) { + abs_dir += "/" + base_dirs[i]; + } + abs_dir += "/" + remainder; + } + } + } else if (startsWith(relative_str, ".")) { + // Property path? + if (err) { + (*err) += "A path starting with `.` is not allowed for Prim path.\n"; + } + return false; + } else if (startsWith(relative_str, "/")) { + // Input path is already absolute. + abs_dir = relative_str; + } else { + // Guess relative path(e.g. "muda", "bora/dora") + // TODO: Check Path contains valid characters. + abs_dir = base_str + "/" + relative_str; + } + + (*abs_path) = Path(abs_dir, relative_path.prop_part()); + + return true; +} + +bool ValidatePath(const Path &path, std::string *err) { + return ValidatePrimPath(path, err) && ValidatePropPath(path, err); +} + +bool ValidatePrimPath(const Path &path, std::string *err) { + if (!path.is_valid()) { + if (err) { + (*err) = "Path is invalid."; + } + return false; + } + + if (!path.is_prim_path()) { + if (err) { + (*err) = "Path is not Prim path."; + } + return false; + } + + const std::vector element_names = split(path.prim_part(), "/"); + + for (size_t i = 0; i < element_names.size(); i++) { + if (!ValidatePrimElementName(element_names[i])) { + if (err) { + (*err) = "Prim path is not composed of valid identifiers."; + } + + return false; + } + } + + return true; +} + +bool ValidatePropPath(const Path &path, std::string *err) { + if (path.prop_part() == ":") { + if (err) { + (*err) = "Proparty path is composed of namespace delimiter only(`:`)."; + } + return false; + } + + if (startsWith(path.prop_part(), ":")) { + if (err) { + (*err) = "Property path starts with namespace delimiter(`:`)."; + } + return false; + } + + if (endsWith(path.prop_part(), ":")) { + if (err) { + (*err) = "Property path ends with namespace delimiter(`:`)."; + } + return false; + } + + if (contains_str(path.prop_part(), "::")) { + if (err) { + (*err) = "Empty path among namespace delimiters(`::`) in Property path."; + } + return false; + } + + // TODO: more validation + + return true; + +} + +} // namespace pathutil +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/path-util.hh b/contrib/tinyusdz/tinyusdz_repo/src/path-util.hh new file mode 100644 index 000000000..6eecde7e0 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/path-util.hh @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - Present, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +// Utility functions for Path + +#include "prim-types.hh" + +namespace tinyusdz { +namespace pathutil { + +/// +/// Validate path. `err` will be filled when Path is an invalid one. +/// +bool ValidatePath(const Path &path, std::string *err); + +/// +/// Validate Prim path(`Path::prim_part()`). `err` will be filled when Prim path is invalid(e.g. contains invalid character: "/dora%bora|muda"). +/// +bool ValidatePrimPath(const Path &path, std::string *err); + +/// +/// Validate Prim property path(`Path::prop_part()`). `err` will be filled when Prim property path is invalid(e.g. contains invalid character: "/dora%bora|muda"). +/// +bool ValidatePropPath(const Path &path, std::string *err); + +/// +/// +/// Construct Path from a string. +/// It splits string into prim_part and prop_part(e.g. "/bora.dora" => "/dora", "bora") if required and constrcut Path object. +/// +/// Use Path::valid() to check if input `path_str` is a valid path string. +/// +Path FromString(const std::string &path_str); + +/// +/// Concatinate two Paths. +/// +Path ConcatPath(const Path &parent, const Path &child); + +/// +/// Replace '../' and produce absolute path +/// +/// @param[in] base_prim_path Base prim path(absolute) +/// @param[in] relative_path Relative prim path. +/// @param[out] abs_path Resolved absolute path. +/// +/// base_prim_path: /root/xform +/// +/// ../bora => /root/bora +/// ../../bora => /bora +/// bora => /root/xform/bora +/// +/// NG +/// +/// - ../../../bora => nest size mismatch +/// - `../` in the middle of relative path(e.g. `/root/../bora`) +/// - `./` (e.g. `./bora`) +/// +/// @return true upon success to resolve relative path. +/// @return false when `base_prim_path` is a relative path or invalid, +/// `relative_path` is an absolute path or invalid, or cannot resolve relative +/// path. +/// +bool ResolveRelativePath(const Path &base_prim_path, const Path &relative_path, + Path *abs_path, std::string *err = nullptr); + +/// +/// Currently ToUnixishPath converts backslash character to forward slash +/// character. +/// +/// /home/tinyusdz => C:/Users/tinyusdz +/// C:\\Users\\tinyusdz => C:/Users/tinyusdz +/// +Path ToUnixishPath(const Path &path); + +} // namespace pathutil +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/pprinter.cc b/contrib/tinyusdz/tinyusdz_repo/src/pprinter.cc new file mode 100644 index 000000000..47d4ae379 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/pprinter.cc @@ -0,0 +1,4541 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2021 - 2022, Syoyo Fujita. +// Copyright 2023, Light Transport Entertainment Inc. +// +// USD ASCII pretty printer. +// +// +#include "pprinter.hh" + +#include "prim-pprint.hh" +#include "prim-types.hh" +#include "str-util.hh" +#include "tiny-format.hh" +#include "usdShade.hh" +#include "value-pprint.hh" +// +#include "common-macros.inc" + +// For fast int/float to ascii +// Default disabled. +// #define TINYUSDZ_LOCAL_USE_JEAIII_ITOA + +#if defined(TINYUSDZ_LOCAL_USE_JEAIII_ITOA) +#include "external/jeaiii_to_text.h" +#endif + +// dtoa_milo does not work well for float types +// (e.g. it prints float 0.01 as 0.009999999997), +// so use floaxie for float types +// TODO: Use floaxie also for double? +#include "external/dtoa_milo.h" + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +// #include "external/floaxie/floaxie/ftoa.h" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// TODO: +// - [ ] Print properties based on lexcographically(USDA) +// - [ ] Refactor variantSet stmt print. +// - [ ] wrap float/double print with `dtos` for accurate float/double value +// stringify. + +namespace tinyusdz { + +namespace { + +#if defined(TINYUSDZ_LOCAL_USE_JEAIII_ITOA) +void itoa(uint32_t n, char *b) { *jeaiii::to_text_from_integer(b, n) = '\0'; } +void itoa(int32_t n, char *b) { *jeaiii::to_text_from_integer(b, n) = '\0'; } +void itoa(uint64_t n, char *b) { *jeaiii::to_text_from_integer(b, n) = '\0'; } +void itoa(int64_t n, char *b) { *jeaiii::to_text_from_integer(b, n) = '\0'; } +#endif + +#if 0 +inline std::string dtos(const float v) { + + char buf[floaxie::max_buffer_size()]; + size_t n = floaxie::ftoa(v, buf); + + return std::string(buf, buf + n); +} +#endif + +inline std::string dtos(const double v) { + char buf[128]; + dtoa_milo(v, buf); + + return std::string(buf); +} + +// Path quote +std::string pquote(const Path &p) { + return wquote(p.full_path_name(), "<", ">"); +} + +} // namespace +} // namespace tinyusdz + +namespace std { + +std::ostream &operator<<(std::ostream &ofs, tinyusdz::Visibility v) { + ofs << to_string(v); + return ofs; +} + +std::ostream &operator<<(std::ostream &ofs, tinyusdz::Extent v) { + ofs << to_string(v); + + return ofs; +} + +std::ostream &operator<<(std::ostream &ofs, const tinyusdz::Path &v) { + ofs << tinyusdz::pquote(v); + + return ofs; +} + +std::ostream &operator<<(std::ostream &ofs, const tinyusdz::LayerOffset &v) { + bool print_offset{true}; + bool print_scale{true}; + + if (std::fabs(v._offset) < std::numeric_limits::epsilon()) { + print_offset = false; + } + + if (std::fabs(v._scale - 1.0) < std::numeric_limits::epsilon()) { + print_scale = false; + } + + if (!print_offset && !print_scale) { + // No need to print LayerOffset. + return ofs; + } + + // TODO: Do not print scale when it is 1.0 + ofs << "("; + if (print_offset && print_scale) { + ofs << "offset = " << tinyusdz::dtos(v._offset) + << ", scale = " << tinyusdz::dtos(v._scale); + } else if (print_offset) { + ofs << "offset = " << tinyusdz::dtos(v._offset); + } else { // print_scale + ofs << "scale = " << tinyusdz::dtos(v._scale); + } + ofs << ")"; + + return ofs; +} + +std::ostream &operator<<(std::ostream &ofs, const tinyusdz::Reference &v) { + ofs << v.asset_path; + if (v.prim_path.is_valid()) { + ofs << v.prim_path; + } + ofs << v.layerOffset; + if (!v.customData.empty()) { + ofs << tinyusdz::print_customData(v.customData, "customData", + /* indent */ 0); + } + + return ofs; +} + +std::ostream &operator<<(std::ostream &ofs, const tinyusdz::Payload &v) { + if (v.is_none()) { + ofs << "None"; + } else { + ofs << v.asset_path; + if (v.prim_path.is_valid()) { + ofs << v.prim_path; + } + ofs << v.layerOffset; + } + + return ofs; +} + +std::ostream &operator<<(std::ostream &ofs, const tinyusdz::SubLayer &v) { + ofs << v.assetPath << v.layerOffset; + + return ofs; +} + +std::ostream &operator<<(std::ostream &ofs, + const tinyusdz::value::StringData &v) { +#if 0 + std::string delim = v.single_quote ? "'" : "\""; + + if (v.is_triple_quoted) { + if (v.single_quote) { + if (tinyusdz::hasEscapedTripleQuotes(v.value, /* double quote */false)) { + // Change to use """ + delim = "\"\"\""; + } else { + delim = "'''"; + } + } else { + delim = "\"\"\""; + } + } + + ofs << delim; + ofs << tinyusdz::escapeBackslash(v.value, v.is_triple_quoted); + ofs << delim; +#else + ofs << tinyusdz::buildEscapedAndQuotedStringForUSDA(v.value); +#endif + + return ofs; +} + +std::ostream &operator<<(std::ostream &ofs, const tinyusdz::Layer &layer) { + ofs << to_string(layer); + return ofs; +} + +} // namespace std + +namespace tinyusdz { + +namespace pprint { + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wglobal-constructors" +#endif + +static std::string sIndentString = " "; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +std::string Indent(uint32_t n) { + std::stringstream ss; + + for (uint32_t i = 0; i < n; i++) { + ss << sIndentString; + } + + return ss.str(); +} + +void SetIndentString(const std::string &s) { sIndentString = s; } + +} // namespace pprint + +template +std::string print_typed_timesamples(const TypedTimeSamples &v, + const uint32_t indent = 0) { + std::stringstream ss; + + ss << "{\n"; + + const auto &samples = v.get_samples(); + + for (size_t i = 0; i < samples.size(); i++) { + ss << pprint::Indent(indent + 1) << samples[i].t << ": "; + if (samples[i].blocked) { + ss << "None"; + } else { + ss << samples[i].value; + } + ss << ",\n"; + } + + ss << pprint::Indent(indent) << "}\n"; + + return ss.str(); +} + +template +std::string print_typed_token_timesamples(const TypedTimeSamples &v, + const uint32_t indent = 0) { + std::stringstream ss; + + ss << "{\n"; + + const auto &samples = v.get_samples(); + + for (size_t i = 0; i < samples.size(); i++) { + ss << pprint::Indent(indent + 1) << samples[i].t << ": "; + if (samples[i].blocked) { + ss << "None"; + } else { + ss << quote(to_string(samples[i].value)); + } + ss << ",\n"; + } + + ss << pprint::Indent(indent) << "}\n"; + + return ss.str(); +} + +static std::string print_str_timesamples(const TypedTimeSamples &v, + const uint32_t indent = 0) { + std::stringstream ss; + + ss << "{\n"; + + const auto &samples = v.get_samples(); + + for (size_t i = 0; i < samples.size(); i++) { + ss << pprint::Indent(indent + 1) << samples[i].t << ": "; + if (samples[i].blocked) { + ss << "None"; + } else { + ss << buildEscapedAndQuotedStringForUSDA(samples[i].value); + } + ss << ",\n"; + } + + ss << pprint::Indent(indent) << "}\n"; + + return ss.str(); +} + +template +std::string print_animatable(const Animatable &v, + const uint32_t indent = 0) { + std::stringstream ss; + + if (v.is_timesamples()) { + ss << print_typed_timesamples(v.get_timesamples(), indent); + } else if (v.is_blocked()) { + ss << "None"; + } else if (v.is_scalar()) { + T a; + if (!v.get_scalar(&a)) { + return "[Animatable: InternalError]"; + } + ss << a; + } else { + return "[FIXME: Invalid Animatable]"; + } + + return ss.str(); +} + +template +std::string print_animatable_token(const Animatable &v, + const uint32_t indent = 0) { + std::stringstream ss; + + if (v.is_timesamples()) { + ss << print_typed_token_timesamples(v.get_timesamples(), indent); + } else if (v.is_blocked()) { + ss << "None"; + } else if (v.is_scalar()) { + T a; + if (!v.get_scalar(&a)) { + return "[Animatable: InternalError]"; + } + ss << quote(to_string(a)); + } else { + return "[FIXME: Invalid Animatable]"; + } + + return ss.str(); +} + +namespace { + +std::string print_references(const prim::ReferenceList &references, + const uint32_t indent) { + std::stringstream ss; + + auto listEditQual = std::get<0>(references); + auto vars = std::get<1>(references); + + ss << pprint::Indent(indent); + + if (listEditQual != ListEditQual::ResetToExplicit) { + ss << to_string(listEditQual) << " "; + } + + ss << "references = "; + + if (vars.empty()) { + ss << "None"; + } else { + if (vars.size() == 1) { + ss << vars[0]; + } else { + ss << vars; + } + } + ss << "\n"; + + return ss.str(); +} + +std::string print_rel_only(const Relationship &rel, const std::string &name, + uint32_t indent) { + std::stringstream ss; + + ss << "rel " << name; + + if (!rel.has_value()) { + // nothing todo + } else if (rel.is_path()) { + ss << " = " << rel.targetPath; + } else if (rel.is_pathvector()) { + ss << " = " << rel.targetPathVector; + } else if (rel.is_blocked()) { + ss << " = None"; + } else { + ss << "[InternalErrror]"; + } + + if (rel.metas().authored()) { + ss << " (\n" + << print_attr_metas(rel.metas(), indent + 1) << pprint::Indent(indent) + << ")"; + } + + ss << "\n"; + + return ss.str(); +} + +std::string print_relationship(const Relationship &rel, + const ListEditQual &qual, const bool custom, + const std::string &name, uint32_t indent) { + std::stringstream ss; + + ss << pprint::Indent(indent); + + if (custom) { + ss << "custom "; + } + + // List editing + if (qual != ListEditQual::ResetToExplicit) { + ss << to_string(qual) << " "; + } + + if (rel.is_varying_authored()) { + ss << "varying "; + } + + ss << print_rel_only(rel, name, indent); + + return ss.str(); +} + +} // namespace + +std::string print_payload(const prim::PayloadList &payload, + const uint32_t indent) { + std::stringstream ss; + + auto listEditQual = std::get<0>(payload); + auto vars = std::get<1>(payload); + + ss << pprint::Indent(indent); + + if (listEditQual != ListEditQual::ResetToExplicit) { + ss << to_string(listEditQual) << " "; + } + + ss << "payload = "; + if (vars.empty()) { + ss << "None"; + } else { + if (vars.size() == 1) { + ss << vars[0]; + } else { + ss << vars; + } + } + ss << "\n"; + + return ss.str(); +} + +std::string print_prim_metas(const PrimMeta &meta, const uint32_t indent) { + std::stringstream ss; + + if (meta.active) { + ss << pprint::Indent(indent) + << "active = " << to_string(meta.active.value()) << "\n"; + } + + if (meta.clips) { + ss << print_customData(meta.clips.value(), "clips", indent); + } + + if (meta.instanceable) { + ss << pprint::Indent(indent) + << "instanceable = " << to_string(meta.instanceable.value()) << "\n"; + } + + if (meta.hidden) { + ss << pprint::Indent(indent) + << "hidden = " << to_string(meta.hidden.value()) << "\n"; + } + + if (meta.kind) { + ss << pprint::Indent(indent) << "kind = " << quote(meta.get_kind()) << "\n"; + } + + // TODO: UTF-8 ready pprint + if (meta.sceneName) { + ss << pprint::Indent(indent) + << "sceneName = " << quote(meta.sceneName.value()) << "\n"; + } + + // TODO: UTF-8 ready pprint + if (meta.displayName) { + ss << pprint::Indent(indent) + << "displayName = " << quote(meta.displayName.value()) << "\n"; + } + + if (meta.assetInfo) { + ss << print_customData(meta.assetInfo.value(), "assetInfo", indent); + } + + if (meta.inherits) { + ss << pprint::Indent(indent); + auto listEditQual = std::get<0>(meta.inherits.value()); + auto var = std::get<1>(meta.inherits.value()); + + if (listEditQual != ListEditQual::ResetToExplicit) { + ss << to_string(listEditQual) << " "; + } + + if (var.size() == 1) { + // print as scalar + ss << "inherits = " << var[0]; + } else { + ss << "inherits = " << var; + } + ss << "\n"; + } + + if (meta.specializes) { + ss << pprint::Indent(indent); + auto listEditQual = std::get<0>(meta.specializes.value()); + auto var = std::get<1>(meta.specializes.value()); + + if (listEditQual != ListEditQual::ResetToExplicit) { + ss << to_string(listEditQual) << " "; + } + + if (var.size() == 1) { + // print as scalar + ss << "specializes = " << var[0]; + } else { + ss << "specializes = " << var; + } + ss << "\n"; + } + + if (meta.references) { + ss << print_references(meta.references.value(), indent); + } + + if (meta.payload) { + ss << print_payload(meta.payload.value(), indent); + } + + // TODO: only print in usdShade Prims. + if (meta.sdrMetadata) { + ss << print_customData(meta.sdrMetadata.value(), "sdrMetadata", indent); + } + + if (meta.variants) { + ss << print_variantSelectionMap(meta.variants.value(), indent); + } + + if (meta.variantSets) { + ss << pprint::Indent(indent); + auto listEditQual = std::get<0>(meta.variantSets.value()); + const std::vector &vs = + std::get<1>(meta.variantSets.value()); // string[] + + if (listEditQual != ListEditQual::ResetToExplicit) { + ss << to_string(listEditQual) << " "; + } + + ss << "variantSets = "; + + if (vs.empty()) { + ss << "None"; + } else { + ss << to_string(vs); + } + + ss << "\n"; + } + + if (meta.apiSchemas) { + auto schemas = meta.apiSchemas.value(); + + if (schemas.names.size()) { + ss << pprint::Indent(indent) << to_string(schemas.listOpQual) + << " apiSchemas = ["; + + for (size_t i = 0; i < schemas.names.size(); i++) { + if (i != 0) { + ss << ", "; + } + + auto name = std::get<0>(schemas.names[i]); + ss << "\"" << to_string(name); + + auto instanceName = std::get<1>(schemas.names[i]); + + if (!instanceName.empty()) { + ss << ":" << instanceName; + } + + ss << "\""; + } + ss << "]\n"; + } + } + + if (meta.doc) { + ss << pprint::Indent(indent) << "doc = " << to_string(meta.doc.value()) + << "\n"; + } + + if (meta.comment) { + ss << pprint::Indent(indent) + << "comment = " << to_string(meta.comment.value()) << "\n"; + } + + if (meta.customData) { + ss << print_customData(meta.customData.value(), "customData", indent); + } + + for (const auto &item : meta.unregisteredMetas) { + // do not quote + ss << pprint::Indent(indent) << item.first << " = " << item.second << "\n"; + } + + // TODO: deprecate meta.meta and remove it. + for (const auto &item : meta.meta) { + ss << print_meta(item.second, indent + 1, true, item.first); + } + + // for (const auto &item : meta.stringData) { + // ss << pprint::Indent(indent) << to_string(item) << "\n"; + // } + + return ss.str(); +} + +std::string print_attr_metas(const AttrMeta &meta, const uint32_t indent) { + std::stringstream ss; + + if (meta.interpolation) { + ss << pprint::Indent(indent) + << "interpolation = " << quote(to_string(meta.interpolation.value())) + << "\n"; + } + + if (meta.elementSize) { + ss << pprint::Indent(indent) + << "elementSize = " << to_string(meta.elementSize.value()) << "\n"; + } + + if (meta.bindMaterialAs) { + ss << pprint::Indent(indent) + << "bindMaterialAs = " << quote(to_string(meta.bindMaterialAs.value())) + << "\n"; + } + + if (meta.connectability) { + ss << pprint::Indent(indent) + << "connectability = " << quote(to_string(meta.connectability.value())) + << "\n"; + } + + if (meta.displayName) { + ss << pprint::Indent(indent) + << "displayName = " << quote(meta.displayName.value()) << "\n"; + } + + if (meta.outputName) { + ss << pprint::Indent(indent) + << "outputName = " << quote(to_string(meta.outputName.value())) << "\n"; + } + + if (meta.renderType) { + ss << pprint::Indent(indent) + << "renderType = " << quote(to_string(meta.renderType.value())) << "\n"; + } + + if (meta.sdrMetadata) { + ss << pprint::Indent(indent) + << print_customData(meta.sdrMetadata.value(), "sdrMetadata", indent); + } + + if (meta.hidden) { + ss << pprint::Indent(indent) + << "hidden = " << to_string(meta.hidden.value()) << "\n"; + } + + if (meta.comment) { + ss << pprint::Indent(indent) + << "comment = " << to_string(meta.comment.value()) << "\n"; + } + + if (meta.weight) { + ss << pprint::Indent(indent) << "weight = " << dtos(meta.weight.value()) + << "\n"; + } + + if (meta.customData) { + ss << print_customData(meta.customData.value(), "customData", indent); + } + + // other user defined metadataum. + for (const auto &item : meta.meta) { + // attribute meta does not emit type_name + ss << print_meta(item.second, indent, /* emit_type_name */false, item.first); + } + + for (const auto &item : meta.stringData) { + ss << pprint::Indent(indent) << to_string(item) << "\n"; + } + + return ss.str(); +} + +template +std::string print_typed_attr(const TypedAttribute> &attr, + const std::string &name, const uint32_t indent) { + std::stringstream ss; + + if (attr.authored()) { + ss << pprint::Indent(indent); + + ss << value::TypeTraits::type_name() << " " << name; + + if (attr.is_blocked()) { + ss << " = None"; + } else if (attr.is_connection()) { + ss << ".connect = "; + const std::vector &paths = attr.get_connections(); + if (paths.size() == 1) { + ss << paths[0]; + } else if (paths.size() == 0) { + ss << "[InternalError]"; + } else { + ss << paths; + } + + } else { + auto pv = attr.get_value(); + + if (pv) { + if (pv.value().is_timesamples()) { + ss << ".timeSamples = " + << print_typed_timesamples(pv.value().get_timesamples(), indent); + } else { + T a; + if (pv.value().get_scalar(&a)) { + ss << " = " << a; + } else { + ss << " = [InternalError]"; + } + } + } + } + + if (attr.metas().authored()) { + ss << "(\n" + << print_attr_metas(attr.metas(), indent + 1) << pprint::Indent(indent) + << ")"; + } + ss << "\n"; + } + + return ss.str(); +} + +static std::string print_str_attr( + const TypedAttribute> &attr, + const std::string &name, const uint32_t indent) { + std::stringstream ss; + + if (attr.authored()) { + ss << pprint::Indent(indent); + + ss << value::TypeTraits::type_name() << " " << name; + + if (attr.is_blocked()) { + ss << " = None"; + } else if (attr.is_connection()) { + ss << ".connect = "; + const std::vector &paths = attr.get_connections(); + if (paths.size() == 1) { + ss << paths[0]; + } else if (paths.size() == 0) { + ss << "[InternalError]"; + } else { + ss << paths; + } + + } else { + auto pv = attr.get_value(); + + if (pv) { + if (pv.value().is_timesamples()) { + ss << ".timeSamples = " + << print_str_timesamples(pv.value().get_timesamples(), indent); + } else { + std::string a; + if (pv.value().get_scalar(&a)) { + // Do not use operator<<(std::string) + ss << " = " << tinyusdz::buildEscapedAndQuotedStringForUSDA(a); + } else { + ss << " = [InternalError]"; + } + } + } + } + + if (attr.metas().authored()) { + ss << "(\n" + << print_attr_metas(attr.metas(), indent + 1) << pprint::Indent(indent) + << ")"; + } + ss << "\n"; + } + + return ss.str(); +} + +#if 0 +template +std::string print_typed_token_attr(const TypedAttribute> &attr, const std::string &name, const uint32_t indent) { + + std::stringstream ss; + + if (attr.value) { + + ss << pprint::Indent(indent); + + ss << "token " << name; + + if (attr.is_blocked()) { + ss << " = None"; + } else if (!attr.define_only) { + ss << " = "; + if (attr.value.value().is_timesamples()) { + ss << print_token_timesamples(attr.value.value().ts, indent+1); + } else { + ss << quote(to_string(attr.value.value().value)); + } + } + + if (attr.meta.authored()) { + ss << " (\n" << print_attr_metas(attr.meta, indent + 1) << pprint::Indent(indent) << ")"; + } + ss << "\n"; + } + + return ss.str(); +} +#endif + +template +std::string print_typed_attr(const TypedAttribute &attr, + const std::string &name, const uint32_t indent) { + std::stringstream ss; + + if (attr.authored()) { + ss << pprint::Indent(indent); + + ss << "uniform "; + + ss << value::TypeTraits::type_name() << " " << name; + + if (attr.is_blocked()) { + ss << " = None"; + } else if (attr.is_connection()) { + ss << ".connect = "; + const std::vector &paths = attr.get_connections(); + if (paths.size() == 1) { + ss << paths[0]; + } else if (paths.size() == 0) { + ss << "[InternalError]"; + } else { + ss << paths; + } + } else if (attr.is_value_empty()) { + // nothing to do + + } else { + auto pv = attr.get_value(); + if (pv) { + ss << " = " << pv.value(); + } + } + + if (attr.metas().authored()) { + ss << " (\n" + << print_attr_metas(attr.metas(), indent + 1) << pprint::Indent(indent) + << ")"; + } + ss << "\n"; + } + + return ss.str(); +} + +#if 0 +template +std::string print_typed_token_attr(const TypedAttribute &attr, const std::string &name, const uint32_t indent) { + + std::stringstream ss; + + if (attr.authored()) { + + auto pv = attr.get(); + + ss << pprint::Indent(indent); + + ss << "uniform token " << name; + + + if (attr.is_blocked()) { + ss << " = None"; + } else { + if (pv) { + ss << " = " << to_string(pv.value()); + } + } + + if (attr.meta.authored()) { + ss << " (\n" << print_attr_metas(attr.meta, indent + 1) << pprint::Indent(indent) << ")"; + } + ss << "\n"; + } + + return ss.str(); +} +#endif + +template +std::string print_typed_attr( + const TypedAttributeWithFallback> &attr, + const std::string &name, const uint32_t indent) { + std::stringstream ss; + + if (attr.authored()) { + ss << pprint::Indent(indent); + + ss << value::TypeTraits::type_name() << " " << name; + + if (attr.is_connection()) { + ss << ".connect = "; + + const std::vector &paths = attr.get_connections(); + if (paths.size() == 1) { + ss << paths[0]; + } else if (paths.size() == 0) { + ss << "[InternalError]"; + } else { + ss << paths; + } + + } else if (attr.is_value_empty()) { + // nothing to do + } else { + auto v = attr.get_value(); + + if (v.is_timesamples()) { + ss << ".timeSamples"; + } + + ss << " = " << print_animatable(v, indent); + } + + if (attr.metas().authored()) { + ss << " (\n" + << print_attr_metas(attr.metas(), indent + 1) << pprint::Indent(indent) + << ")"; + } + ss << "\n"; + } + + return ss.str(); +} + +template +std::string print_typed_terminal_attr(const TypedTerminalAttribute &attr, + const std::string &name, + const uint32_t indent) { + std::stringstream ss; + + if (attr.authored()) { + ss << pprint::Indent(indent); + + if (attr.has_actual_type()) { + ss << attr.get_actual_type_name() << " " << name; + } else { + ss << value::TypeTraits::type_name() << " " << name; + } + + if (attr.metas().authored()) { + ss << " (\n" + << print_attr_metas(attr.metas(), indent + 1) << pprint::Indent(indent) + << ")"; + } + ss << "\n"; + } + + return ss.str(); +} + +template +std::string print_typed_attr(const TypedAttributeWithFallback &attr, + const std::string &name, const uint32_t indent) { + std::stringstream ss; + + if (attr.authored()) { + ss << pprint::Indent(indent); + + ss << "uniform "; + + ss << value::TypeTraits::type_name() << " " << name; + + if (attr.is_blocked()) { + ss << " = None"; + } else if (attr.is_connection()) { + ss << ".connect = "; + + const std::vector &paths = attr.get_connections(); + if (paths.size() == 1) { + ss << paths[0]; + } else if (paths.size() == 0) { + ss << "[InternalError]"; + } else { + ss << paths; + } + } else { + ss << " = " << attr.get_value(); + } + + if (attr.metas().authored()) { + ss << " (\n" + << print_attr_metas(attr.metas(), indent + 1) << pprint::Indent(indent) + << ")"; + } + ss << "\n"; + } + + return ss.str(); +} + +template +std::string print_typed_token_attr( + const TypedAttributeWithFallback> &attr, + const std::string &name, const uint32_t indent) { + std::stringstream ss; + + if (attr.authored()) { + if (attr.is_connection()) { + ss << pprint::Indent(indent); + + ss << "token " << name; + + ss << ".connect = "; + const std::vector &paths = attr.get_connections(); + if (paths.size() == 1) { + ss << paths[0]; + } else if (paths.size() == 0) { + ss << "[InternalError]"; + } else { + ss << paths; + } + + } else { + auto v = attr.get_value(); + + ss << pprint::Indent(indent); + + ss << "token " << name; + + if (v.is_timesamples()) { + ss << ".timeSamples"; + } + + ss << " = " << print_animatable_token(v, indent); + } + + if (attr.metas().authored()) { + ss << " (\n" + << print_attr_metas(attr.metas(), indent + 1) << pprint::Indent(indent) + << ")"; + } + ss << "\n"; + } + + return ss.str(); +} + +template +std::string print_typed_token_attr(const TypedAttributeWithFallback &attr, + const std::string &name, + const uint32_t indent) { + std::stringstream ss; + + if (attr.authored()) { + if (attr.is_connection()) { + ss << pprint::Indent(indent); + + ss << "token " << name; + + ss << ".connect = "; + const std::vector &paths = attr.get_connections(); + if (paths.size() == 1) { + ss << paths[0]; + } else if (paths.size() == 0) { + ss << "[InternalError]"; + } else { + ss << paths; + } + } else { + ss << pprint::Indent(indent); + + ss << "uniform token " << name; + + if (attr.is_blocked()) { + ss << " = None"; + } else { + ss << " = " << quote(to_string(attr.get_value())); + } + } + + if (attr.metas().authored()) { + ss << " (\n" + << print_attr_metas(attr.metas(), indent + 1) << pprint::Indent(indent) + << ")"; + } + ss << "\n"; + } + + return ss.str(); +} + +std::string print_timesamples(const value::TimeSamples &v, + const uint32_t indent) { + std::stringstream ss; + + ss << "{\n"; + + for (size_t i = 0; i < v.size(); i++) { + ss << pprint::Indent(indent + 1); + ss << v.get_samples()[i].t << ": " + << value::pprint_value(v.get_samples()[i].value); + ss << ",\n"; // USDA allow ',' for the last item + } + ss << pprint::Indent(indent) << "}\n"; + + return ss.str(); +} + +std::string print_rel_prop(const Property &prop, const std::string &name, + uint32_t indent) { + std::stringstream ss; + + if (!prop.is_relationship()) { + return ss.str(); + } + + ss << pprint::Indent(indent); + + if (prop.has_custom()) { + ss << "custom "; + } + + // List editing + if (prop.get_listedit_qual() != ListEditQual::ResetToExplicit) { + ss << to_string(prop.get_listedit_qual()) << " "; + } + + const Relationship &rel = prop.get_relationship(); + if (rel.is_varying_authored()) { + ss << "varying "; + } + + ss << print_rel_only(rel, name, indent); + + return ss.str(); +} + +std::string print_prop(const Property &prop, const std::string &prop_name, + uint32_t indent) { + std::stringstream ss; + + if (prop.is_relationship()) { + ss << print_rel_prop(prop, prop_name, indent); + + // Attribute or AttributeConnection + } else if (prop.is_attribute() || prop.is_connection()) { + const Attribute &attr = prop.get_attribute(); + + ss << pprint::Indent(indent); + + if (prop.has_custom()) { + ss << "custom "; + } + + if (attr.variability() == Variability::Uniform) { + ss << "uniform "; + } else if (attr.is_varying_authored()) { + // For Attribute, `varying` is the default variability and does not shown + // in USDA do nothing + } + + std::string ty; + + ty = attr.type_name(); + ss << ty << " " << prop_name; + + if (attr.is_connection()) { + ss << ".connect = "; + + const std::vector &paths = attr.connections(); + if (paths.size() == 1) { + ss << paths[0]; + } else if (paths.size() == 0) { + ss << "[InternalError]"; + } else { + ss << paths; + } + } else if (prop.is_empty()) { + // Nothing to do + } else { + // has value content + + if (attr.get_var().is_timesamples()) { + ss << ".timeSamples"; + } + ss << " = "; + + if (attr.get_var().is_timesamples()) { + ss << print_timesamples(attr.get_var().ts_raw(), indent); + } else if (attr.is_blocked()) { + ss << "None"; + } else { + // is_scalar + ss << value::pprint_value(attr.get_var().value_raw()); + } + } + + if (prop.get_attribute().metas().authored()) { + ss << " (\n" + << print_attr_metas(prop.get_attribute().metas(), indent + 1) + << pprint::Indent(indent) << ")"; + } + ss << "\n"; + } else { + ss << "[Invalid Property] " << prop_name << "\n"; + } + + return ss.str(); +} + +std::string print_props(const std::map &props, + uint32_t indent) { + std::stringstream ss; + + for (const auto &item : props) { + const Property &prop = item.second; + + ss << print_prop(prop, item.first, indent); + } + + return ss.str(); +} + +// Print user-defined (custom) properties. +std::string print_props(const std::map &props, + std::set &tok_table, + const std::vector &propNames, + uint32_t indent) { + std::stringstream ss; + + if (propNames.size()) { + for (size_t i = 0; i < propNames.size(); i++) { + if (tok_table.count(propNames[i].str())) { + continue; + } + + const auto it = props.find(propNames[i].str()); + if (it != props.end()) { + ss << print_prop(it->second, it->first, indent); + + tok_table.insert(propNames[i].str()); + } + } + } else { + ss << print_props(props, indent); + } + + return ss.str(); +} + +std::string print_xformOpOrder(const std::vector &xformOps, + const uint32_t indent) { + std::stringstream ss; + + if (xformOps.size()) { + ss << pprint::Indent(indent) << "uniform token[] xformOpOrder = ["; + for (size_t i = 0; i < xformOps.size(); i++) { + if (i > 0) { + ss << ", "; + } + + auto xformOp = xformOps[i]; + ss << "\""; + if (xformOp.inverted) { + ss << "!invert!"; + } + ss << to_string(xformOp.op_type); + if (!xformOp.suffix.empty()) { + ss << ":" << xformOp.suffix; + } + ss << "\""; + } + ss << "]\n"; + } + + return ss.str(); +} + +std::string print_xformOps(const std::vector &xformOps, + const uint32_t indent) { + std::stringstream ss; + + // To prevent printing xformOp attributes multiple times. + std::set printed_vars; + + // xforms props + if (xformOps.size()) { + for (size_t i = 0; i < xformOps.size(); i++) { + const auto xformOp = xformOps[i]; + + if (xformOp.op_type == XformOp::OpType::ResetXformStack) { + // No need to print value. + continue; + } + + std::string varname = to_string(xformOp.op_type); + if (!xformOp.suffix.empty()) { + varname += ":" + xformOp.suffix; + } + + if (printed_vars.count(varname)) { + continue; + } + + printed_vars.insert(varname); + + ss << pprint::Indent(indent); + + ss << xformOp.get_value_type_name() << " "; + + ss << varname; + + if (xformOp.is_timesamples()) { + ss << ".timeSamples"; + } + + ss << " = "; + + if (xformOp.is_timesamples()) { + if (auto pv = xformOp.get_timesamples()) { + ss << print_timesamples(pv.value(), indent); + } else { + ss << "[InternalError]"; + } + } else { + if (auto pv = xformOp.get_scalar()) { + ss << value::pprint_value(pv.value(), indent); + } else { + ss << "[InternalError]"; + } + } + + ss << "\n"; + } + } + + // uniform token[] xformOpOrder + ss << print_xformOpOrder(xformOps, indent); + + return ss.str(); +} + +#if 0 +static std::string print_xformOp(const std::vector &xformOps, + const std::string &prop_name, + const uint32_t indent, + std::set &table) { + std::stringstream ss; + + if (xformOps.empty()) { + return ss.str(); + } + + // simple linear search + for (size_t i = 0; i < xformOps.size(); i++) { + const auto xformOp = xformOps[i]; + + if (xformOp.op_type == XformOp::OpType::ResetXformStack) { + // No need to print value. + continue; + } + + std::string varname = to_string(xformOp.op_type); + if (!xformOp.suffix.empty()) { + varname += ":" + xformOp.suffix; + } + + if (prop_name != varname) { + continue; + } + + ss << pprint::Indent(indent); + + ss << xformOp.get_value_type_name() << " "; + + ss << varname; + + if (xformOp.is_timesamples()) { + ss << ".timeSamples"; + } + + ss << " = "; + + if (xformOp.is_timesamples()) { + if (auto pv = xformOp.get_timesamples()) { + ss << print_timesamples(pv.value(), indent); + } else { + ss << "[InternalError]"; + } + } else { + if (auto pv = xformOp.get_scalar()) { + ss << value::pprint_value(pv.value(), indent); + } else { + ss << "[InternalError]"; + } + } + + ss << "\n"; + + table.insert(prop_name); + break; + } + + return ss.str(); +} +#endif + +std::string print_material_binding(const MaterialBinding *mb, const uint32_t indent) { + if (!mb) { + return std::string(); + } + + std::stringstream ss; + + if (mb->materialBinding) { + ss << print_relationship(mb->materialBinding.value(), + mb->materialBinding.value().get_listedit_qual(), + /* custom */ false, kMaterialBinding, indent); + } + + if (mb->materialBindingPreview) { + ss << print_relationship( + mb->materialBindingPreview.value(), + mb->materialBindingPreview.value().get_listedit_qual(), + /* custom */ false, kMaterialBindingPreview, indent); + } + + if (mb->materialBindingFull) { + ss << print_relationship( + mb->materialBindingFull.value(), + mb->materialBindingFull.value().get_listedit_qual(), + /* custom */ false, kMaterialBindingFull, indent); + } + + // NOTE: matb does not include "material:binding", "material:binding:preview" and "material:binding:full" + for (const auto &matb : mb->materialBindingMap()) { + if (matb.first.empty()) { + // this should not happen + continue; + } + + std::string matb_name = kMaterialBinding + std::string(":") + matb.first; + + ss << print_relationship( + matb.second, + matb.second.get_listedit_qual(), + /* custom */ false, matb_name, indent); + + } + + // TODO: sort by collection name? + for (const auto &collection : mb->materialBindingCollectionMap()) { + + std::string purpose_name; + if (!collection.first.empty()) { + purpose_name = std::string(":") + collection.first; + } + + for (size_t i = 0; i < collection.second.size(); i++) { + std::string coll_name = collection.second.keys()[i]; + + const Relationship *rel{nullptr}; + if (!collection.second.at(i, &rel)) { + // this should not happen though. + continue; + } + + std::string rel_name; + + if (coll_name.empty()) { + rel_name = kMaterialBindingCollection + purpose_name; + } else { + rel_name = kMaterialBindingCollection + std::string(":") + coll_name + purpose_name; + } + + ss << print_relationship( + *rel, + rel->get_listedit_qual(), + /* custom */ false, rel_name, indent); + } + } + + return ss.str(); +} + +std::string print_collection(const Collection *coll, const uint32_t indent) { + std::stringstream ss; + + if (!coll) { + return std::string(); + } + + const auto &instances = coll->instances(); + + for (size_t i = 0; i < instances.size(); i++) { + std::string name = instances.keys()[i]; + + CollectionInstance instance; + if (!instances.at(i, &instance)) { + continue; + } + + std::string prefix = "collection"; + if (name.size()) { + prefix += ":" + name; + } + + if (instance.expansionRule.authored()) { + ss << print_typed_token_attr(instance.expansionRule, prefix + ":expansionRule", indent); + + } + + + if (instance.includeRoot.authored()) { + ss << print_typed_attr(instance.includeRoot, prefix + ":includeRoot", indent); + } + + if (instance.includes) { + ss << print_relationship( + instance.includes.value(), + instance.includes.value().get_listedit_qual(), + /* custom */ false, prefix + ":includes", indent); + + } + + if (instance.excludes) { + ss << print_relationship( + instance.excludes.value(), + instance.excludes.value().get_listedit_qual(), + /* custom */ false, prefix + ":excludes", indent); + + } + } + + return ss.str(); +} + +template +std::string print_gprim_predefined(const T &gprim, const uint32_t indent) { + std::stringstream ss; + + // properties + ss << print_typed_attr(gprim.doubleSided, "doubleSided", indent); + ss << print_typed_token_attr(gprim.orientation, "orientation", indent); + ss << print_typed_token_attr(gprim.purpose, "purpose", indent); + ss << print_typed_attr(gprim.extent, "extent", indent); + + ss << print_typed_token_attr(gprim.visibility, "visibility", indent); + + ss << print_material_binding(&gprim, indent); + + ss << print_collection(&gprim, indent); + + if (gprim.proxyPrim.authored()) { + const Relationship &rel = gprim.proxyPrim.relationship(); + ss << print_relationship(rel, rel.get_listedit_qual(), /* custom */ false, + "proxyPrim", indent); + } + + ss << print_xformOps(gprim.xformOps, indent); + + return ss.str(); +} + +#if 0 +static bool emit_gprim_predefined(std::stringstream &ss, const GPrim *gprim, + const std::string &prop_name, + const uint32_t indent, + std::set &table) { + if (prop_name == "doubleSided") { + ss << print_typed_attr(gprim->doubleSided, "doubleSided", indent); + table.insert("doubleSided"); + } else if (prop_name == "orientation") { + ss << print_typed_token_attr(gprim->orientation, "orientation", indent); + table.insert("orientation"); + } else if (prop_name == "purpose") { + ss << print_typed_token_attr(gprim->purpose, "purpose", indent); + table.insert("purpose"); + } else if (prop_name == "extent") { + ss << print_typed_attr(gprim->extent, "extent", indent); + table.insert("extent"); + } else if (prop_name == "visibility") { + ss << print_typed_token_attr(gprim->visibility, "visibility", indent); + table.insert("visibility"); + } else if (prop_name == "material:binding") { + if (gprim->materialBinding) { + ss << print_relationship( + gprim->materialBinding.value(), + gprim->materialBinding.value().get_listedit_qual(), + /* custom */ false, "material:binding", indent); + table.insert("material:binding"); + } + } else if (prop_name == "material:binding:collection") { + if (gprim->materialBindingCollection) { + ss << print_relationship( + gprim->materialBindingCollection.value(), + gprim->materialBindingCollection.value().get_listedit_qual(), + /* custom */ false, "material:binding:collection", indent); + table.insert("material:binding:collection"); + } + } else if (prop_name == "material:binding:preview") { + if (gprim->materialBindingPreview) { + ss << print_relationship( + gprim->materialBindingPreview.value(), + gprim->materialBindingPreview.value().get_listedit_qual(), + /* custom */ false, "material:binding:preview", indent); + table.insert("material:binding:preview"); + } + } else if (prop_name == "proxyPrim") { + if (gprim->proxyPrim.authored()) { + const Relationship &rel = gprim->proxyPrim.relationship(); + ss << print_relationship(rel, rel.get_listedit_qual(), /* custom */ false, + "proxyPrim", indent); + table.insert("proxyPrim"); + } + } else if (prop_name == "xformOpOrder") { + ss << print_xformOpOrder(gprim->xformOps, indent); + table.insert("xformOpOrder"); + } else if (startsWith(prop_name, "xformOp:")) { + ss << print_xformOp(gprim->xformOps, prop_name, indent, table); + } else { + // not found + return false; + } + + return true; +} +#endif + +// TODO: Move to value-pprint.cc + +std::string to_string(bool v) { + if (v) { + return "true"; + } else { + return "false"; + } +} + +std::string to_string(int32_t v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(uint32_t v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(int64_t v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(uint64_t v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::int2 &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::int3 &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::int4 &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::uint2 &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::uint3 &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::uint4 &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::float2 &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::float3 &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::float4 &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::double2 &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::double3 &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::double4 &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::texcoord2h &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::texcoord2f &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::texcoord2d &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::texcoord3h &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::texcoord3f &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::texcoord3d &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::matrix2f &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::matrix3f &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::matrix4f &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::matrix2d &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::matrix3d &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const value::matrix4d &v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + +std::string to_string(const APISchemas::APIName &name) { + std::string s; + + switch (name) { + case APISchemas::APIName::SkelBindingAPI: { + s = "SkelBindingAPI"; + break; + } + case APISchemas::APIName::CollectionAPI: { + s = "CollectionAPI"; + break; + } + case APISchemas::APIName::MaterialBindingAPI: { + s = "MaterialBindingAPI"; + break; + } + case APISchemas::APIName::ShapingAPI: { + s = "ShapingAPI"; + break; + } + case APISchemas::APIName::Preliminary_AnchoringAPI: { + s = "Preliminary_AnchoringAPI"; + break; + } + case APISchemas::APIName::Preliminary_PhysicsColliderAPI: { + s = "Preliminary_PhysicsColliderAPI"; + break; + } + case APISchemas::APIName::Preliminary_PhysicsRigidBodyAPI: { + s = "Preliminary_PhysicsRigidBodyAPI"; + break; + } + case APISchemas::APIName::Preliminary_PhysicsMaterialAPI: { + s = "Preliminary_PhysicsMaterialAPI"; + break; + } + } + + return s; +} + +std::string to_string(const CustomDataType &custom) { + return print_customData(custom, "", 0); +} + +std::string to_string(const value::StringData &s) { + std::stringstream ss; + ss << s; + return ss.str(); +} + +std::string to_string(const std::string &v) { + // TODO: Escape `"` character. + + // Escape backslash + return quote(escapeBackslash(v)); +} + +std::string to_string(const Reference &v) { + std::stringstream ss; + + ss << v.asset_path; + if (v.prim_path.is_valid()) { + ss << v.prim_path; + } + + ss << v.layerOffset; + + if (!v.customData.empty()) { + // TODO: Indent + ss << print_customData(v.customData, "customData", /* indent */ 0); + } + + return ss.str(); +} + +std::string to_string(const Payload &v) { + std::stringstream ss; + + if (v.is_none()) { + // pxrUSD serialize and prints 'None' for payload by filling all members in Payload empty. + ss << "None"; + + } else { + ss << v.asset_path; + if (v.prim_path.is_valid()) { + ss << v.prim_path; + } + + ss << v.layerOffset; + } + + return ss.str(); +} + +std::string print_variantSelectionMap(const VariantSelectionMap &m, + const uint32_t indent) { + std::stringstream ss; + + if (m.empty()) { + return ss.str(); + } + + ss << pprint::Indent(indent) << "variants = {\n"; + for (const auto &item : m) { + ss << pprint::Indent(indent + 1) << "string " << item.first << " = " + << quote(item.second) << "\n"; + } + ss << pprint::Indent(indent) << "}\n"; + + return ss.str(); +} + +std::string print_customData(const CustomDataType &customData, + const std::string &dict_name, + const uint32_t indent) { + std::stringstream ss; + + ss << pprint::Indent(indent); + if (!dict_name.empty()) { + std::string name = dict_name; + + if (!isValidIdentifier(name)) { + // May contain "/", quote it + name = quote(name); + } + + ss << name << " = {\n"; + } else { + ss << "{\n"; + } + for (const auto &item : customData) { + ss << print_meta(item.second, indent + 1, true, item.first); + } + ss << pprint::Indent(indent) << "}\n"; + + return ss.str(); +} + +std::string print_meta(const MetaVariable &meta, const uint32_t indent, bool emit_type_name, + const std::string &varname) { + std::stringstream ss; + + // ss << "TODO: isObject " << meta.is_object() << ", isValue " << + // meta.IsValue() << "\n"; + + // Use varname if meta.name is empty + std::string name = meta.get_name(); + if (name.empty()) { + name = varname; + } + + if (name.empty()) { + name = "[ERROR:EmptyName]"; + } + + if (auto pv = meta.get_value()) { + // dict + if (!isValidIdentifier(name)) { + // May contain "/", quote it + name = quote(name); + } + ss << pprint::Indent(indent) << "dictionary " << name << " = {\n"; + for (const auto &item : pv.value()) { + ss << print_meta(item.second, indent + 1, /* emit_type_name */true, item.first); + } + ss << pprint::Indent(indent) << "}\n"; + } else { + ss << pprint::Indent(indent); + if (emit_type_name) { + ss << meta.type_name() << " "; + } + ss << name << " = " + << pprint_value(meta.get_raw_value()) << "\n"; + } + + return ss.str(); +} + +std::string to_string(tinyusdz::GeomMesh::InterpolateBoundary v) { + std::string s; + + switch (v) { + case tinyusdz::GeomMesh::InterpolateBoundary::InterpolateBoundaryNone: { + s = "none"; + break; + } + case tinyusdz::GeomMesh::InterpolateBoundary::EdgeAndCorner: { + s = "edgeAndCorner"; + break; + } + case tinyusdz::GeomMesh::InterpolateBoundary::EdgeOnly: { + s = "edgeOnly"; + break; + } + } + + return s; +} + +std::string to_string(tinyusdz::GeomMesh::SubdivisionScheme v) { + std::string s; + + switch (v) { + case tinyusdz::GeomMesh::SubdivisionScheme::CatmullClark: { + s = "catmullClark"; + break; + } + case tinyusdz::GeomMesh::SubdivisionScheme::Loop: { + s = "loop"; + break; + } + case tinyusdz::GeomMesh::SubdivisionScheme::Bilinear: { + s = "bilinear"; + break; + } + case tinyusdz::GeomMesh::SubdivisionScheme::SubdivisionSchemeNone: { + s = "none"; + break; + } + } + + return s; +} + +std::string to_string(tinyusdz::GeomMesh::FaceVaryingLinearInterpolation v) { + std::string s; + + switch (v) { + case tinyusdz::GeomMesh::FaceVaryingLinearInterpolation::CornersPlus1: { + s = "cornersPlus1"; + break; + } + case tinyusdz::GeomMesh::FaceVaryingLinearInterpolation::CornersPlus2: { + s = "cornersPlus2"; + break; + } + case tinyusdz::GeomMesh::FaceVaryingLinearInterpolation::CornersOnly: { + s = "cornersOnly"; + break; + } + case tinyusdz::GeomMesh::FaceVaryingLinearInterpolation::Boundaries: { + s = "boundaries"; + break; + } + case tinyusdz::GeomMesh::FaceVaryingLinearInterpolation:: + FaceVaryingLinearInterpolationNone: { + s = "none"; + break; + } + case tinyusdz::GeomMesh::FaceVaryingLinearInterpolation::All: { + s = "all"; + break; + } + } + + return s; +} + +std::string to_string(tinyusdz::GeomSubset::ElementType v) { + std::string s; + + switch (v) { + case tinyusdz::GeomSubset::ElementType::Face: { + s = "face"; + break; + } + case tinyusdz::GeomSubset::ElementType::Point: { + s = "point"; + break; + } + } + + return s; +} + +std::string to_string(tinyusdz::GeomSubset::FamilyType v) { + std::string s; + + switch (v) { + case tinyusdz::GeomSubset::FamilyType::Partition: { + s = "partition"; + break; + } + case tinyusdz::GeomSubset::FamilyType::NonOverlapping: { + s = "nonOverlapping"; + break; + } + case tinyusdz::GeomSubset::FamilyType::Unrestricted: { + s = "unrestricted"; + break; + } + } + + return s; +} + +std::string to_string(tinyusdz::CollectionInstance::ExpansionRule rule) { + std::string s; + + switch (rule) { + case tinyusdz::CollectionInstance::ExpansionRule::ExplicitOnly: { + s = kExplicitOnly; + break; + } + case tinyusdz::CollectionInstance::ExpansionRule::ExpandPrims: { + s = kExpandPrims; + break; + } + case tinyusdz::CollectionInstance::ExpansionRule::ExpandPrimsAndProperties: { + s = kExpandPrimsAndProperties; + break; + } + } + + return s; +} + +std::string to_string(const tinyusdz::UsdUVTexture::SourceColorSpace v) { + std::string s; + + switch (v) { + case tinyusdz::UsdUVTexture::SourceColorSpace::Auto: { + s = "auto"; + break; + } + case tinyusdz::UsdUVTexture::SourceColorSpace::Raw: { + s = "raw"; + break; + } + case tinyusdz::UsdUVTexture::SourceColorSpace::SRGB: { + s = "sRGB"; + break; + } + } + + return s; +} + +std::string to_string(const tinyusdz::UsdUVTexture::Wrap v) { + std::string s; + + switch (v) { + case tinyusdz::UsdUVTexture::Wrap::UseMetadata: { + s = "useMetadata"; + break; + } + case tinyusdz::UsdUVTexture::Wrap::Black: { + s = "black"; + break; + } + case tinyusdz::UsdUVTexture::Wrap::Clamp: { + s = "clamp"; + break; + } + case tinyusdz::UsdUVTexture::Wrap::Repeat: { + s = "repeat"; + break; + } + case tinyusdz::UsdUVTexture::Wrap::Mirror: { + s = "mirror"; + break; + } + } + + return s; +} + +std::string to_string(tinyusdz::Kind v) { + if (v == tinyusdz::Kind::Model) { + return "model"; + } else if (v == tinyusdz::Kind::Group) { + return "group"; + } else if (v == tinyusdz::Kind::Assembly) { + return "assembly"; + } else if (v == tinyusdz::Kind::Component) { + return "component"; + } else if (v == tinyusdz::Kind::Subcomponent) { + return "subcomponent"; + } else if (v == tinyusdz::Kind::SceneLibrary) { + return "sceneLibrary"; + } else if (v == tinyusdz::Kind::UserDef) { + // Should use PrimMeta::get_kind() to get actual Kind string value. + return "[[InternalError. UserDefKind]]"; + } else { + return "[[InvalidKind]]"; + } +} + +std::string to_string(tinyusdz::Axis v) { + if (v == tinyusdz::Axis::X) { + return "X"; + } else if (v == tinyusdz::Axis::Y) { + return "Y"; + } else if (v == tinyusdz::Axis::Z) { + return "Z"; + } else { + return "[[InvalidAxis]]"; + } +} + +std::string to_string(tinyusdz::Visibility v) { + if (v == tinyusdz::Visibility::Inherited) { + return "inherited"; + } else { + return "invisible"; + } +} + +std::string to_string(tinyusdz::Orientation o) { + if (o == tinyusdz::Orientation::RightHanded) { + return "rightHanded"; + } else { + return "leftHanded"; + } +} + +std::string to_string(tinyusdz::ListEditQual v) { + if (v == tinyusdz::ListEditQual::ResetToExplicit) { + return ""; // unqualified + } else if (v == tinyusdz::ListEditQual::Append) { + return "append"; + } else if (v == tinyusdz::ListEditQual::Add) { + return "add"; + } else if (v == tinyusdz::ListEditQual::Delete) { + return "delete"; + } else if (v == tinyusdz::ListEditQual::Prepend) { + return "prepend"; + } else if (v == tinyusdz::ListEditQual::Order) { + return "order"; + } + + return "[[Invalid ListEditQual value]]"; +} + +std::string to_string(tinyusdz::Interpolation interp) { + switch (interp) { + case Interpolation::Invalid: + return "[[Invalid interpolation value]]"; + case Interpolation::Constant: + return "constant"; + case Interpolation::Uniform: + return "uniform"; + case Interpolation::Varying: + return "varying"; + case Interpolation::Vertex: + return "vertex"; + case Interpolation::FaceVarying: + return "faceVarying"; + } + + // Never reach here though + return "[[Invalid interpolation value]]"; +} + +std::string to_string(tinyusdz::SpecType ty) { + if (SpecType::Attribute == ty) { + return "SpecTypeAttribute"; + } else if (SpecType::Connection == ty) { + return "SpecTypeConnection"; + } else if (SpecType::Expression == ty) { + return "SpecTypeExpression"; + } else if (SpecType::Mapper == ty) { + return "SpecTypeMapper"; + } else if (SpecType::MapperArg == ty) { + return "SpecTypeMapperArg"; + } else if (SpecType::Prim == ty) { + return "SpecTypePrim"; + } else if (SpecType::PseudoRoot == ty) { + return "SpecTypePseudoRoot"; + } else if (SpecType::Relationship == ty) { + return "SpecTypeRelationship"; + } else if (SpecType::RelationshipTarget == ty) { + return "SpecTypeRelationshipTarget"; + } else if (SpecType::Variant == ty) { + return "SpecTypeVariant"; + } else if (SpecType::VariantSet == ty) { + return "SpecTypeVariantSet"; + } + return "SpecTypeInvalid"; +} + +std::string to_string(tinyusdz::Specifier s) { + if (s == tinyusdz::Specifier::Def) { + return "def"; + } else if (s == tinyusdz::Specifier::Over) { + return "over"; + } else if (s == tinyusdz::Specifier::Class) { + return "class"; + } else { + return "[[SpecifierInvalid]]"; + } +} + +std::string to_string(tinyusdz::Permission s) { + if (s == tinyusdz::Permission::Public) { + return "public"; + } else if (s == tinyusdz::Permission::Private) { + return "private"; + } else { + return "[[PermissionInvalid]]"; + } +} + +std::string to_string(tinyusdz::Purpose purpose) { + switch (purpose) { + case Purpose::Default: { + return "default"; + } + case Purpose::Render: { + return "render"; + } + case Purpose::Guide: { + return "guide"; + } + case Purpose::Proxy: { + return "proxy"; + } + } + + // Never reach here though + return "[[Invalid Purpose value]]"; +} + +std::string to_string(tinyusdz::Variability v) { + if (v == tinyusdz::Variability::Varying) { + return "varying"; + } else if (v == tinyusdz::Variability::Uniform) { + return "uniform"; + } else if (v == tinyusdz::Variability::Config) { + return "config"; + } else { + return "\"[[VariabilityInvalid]]\""; + } +} + +std::string to_string(tinyusdz::Extent e) { + std::stringstream ss; + + ss << "[" << e.lower << ", " << e.upper << "]"; + + return ss.str(); +} + +#if 0 +std::string to_string(const tinyusdz::AnimatableVisibility &v, const uint32_t indent) { + if (auto p = nonstd::get_if(&v)) { + return to_string(*p); + } + + if (auto p = nonstd::get_if>(&v)) { + + std::stringstream ss; + + ss << "{"; + + for (size_t i = 0; i < p->times.size(); i++) { + ss << pprint::Indent(indent+2) << p->times[i] << " : " << to_string(p->values[i]) << ", "; + // TODO: indent and newline + } + + ss << pprint::Indent(indent+1) << "}"; + + } + + return "[[??? AnimatableVisibility]]"; +} +#endif + +#if 0 +std::string to_string(const tinyusdz::Klass &klass, uint32_t indent, bool closing_brace) { + std::stringstream ss; + + ss << tinyusdz::pprint::Indent(indent) << "class " << klass.name << " (\n"; + ss << tinyusdz::pprint::Indent(indent) << ")\n"; + ss << tinyusdz::pprint::Indent(indent) << "{\n"; + + for (auto prop : klass.props) { + + if (prop.second.is_relationship()) { + ss << "TODO: Rel\n"; + } else { + //const PrimAttrib &attrib = prop.second.GetAttrib(); +#if 0 // TODO + if (auto p = tinyusdz::primvar::as_basic(&pattr->var)) { + ss << tinyusdz::pprint::Indent(indent); + if (pattr->custom) { + ss << " custom "; + } + if (pattr->uniform) { + ss << " uniform "; + } + ss << " double " << prop.first << " = " << *p; + } else { + ss << "TODO:" << pattr->type_name << "\n"; + } +#endif + } + + ss << "\n"; + } + + if (closing_brace) { + ss << tinyusdz::pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} +#endif + +std::string print_variantSetStmt( + const std::map &vslist, const uint32_t indent) { + std::stringstream ss; + + // ss << "# variantSet.size = " << std::to_string(vslist.size()) << "\n"; + for (const auto &variantSet : vslist) { + if (variantSet.second.variantSet.empty()) { + continue; + } + + ss << pprint::Indent(indent) << "variantSet " << quote(variantSet.first) + << " = {\n"; + + for (const auto &item : variantSet.second.variantSet) { + ss << pprint::Indent(indent + 1) << quote(item.first) << " "; + + if (item.second.metas().authored()) { + ss << "(\n"; + ss << print_prim_metas(item.second.metas(), indent + 2); + ss << pprint::Indent(indent + 1) << ") "; + } + + ss << "{\n"; + + // props + ss << print_props(item.second.properties(), indent + 2); + + // primChildren + // TODO: print child Prims based on `primChildren` Prim metadata + const auto &variantPrimMetas = item.second.metas(); + const auto &variantPrimChildren = item.second.primChildren(); + + if (variantPrimMetas.primChildren.size() == variantPrimChildren.size()) { + std::map primNameTable; + for (size_t i = 0; i < variantPrimChildren.size(); i++) { + primNameTable.emplace(variantPrimChildren[i].element_name(), + &variantPrimChildren[i]); + } + + for (size_t i = 0; i < variantPrimMetas.primChildren.size(); i++) { + value::token nameTok = variantPrimMetas.primChildren[i]; + DCOUT(fmt::format("variantPrimChildren {}/{} = {}", i, + variantPrimMetas.primChildren.size(), + nameTok.str())); + const auto it = primNameTable.find(nameTok.str()); + if (it != primNameTable.end()) { + ss << pprint_value(it->second->data(), indent + 2, + /* closing_brace */ true); + } else { + // TODO: Report warning? + } + } + } else { + for (const auto &child : variantPrimChildren) { + ss << pprint_value(child.data(), indent + 2, + /* closing_brace */ true); + } + } + + // ss << "# variantSet end\n"; + ss << pprint::Indent(indent + 1) << "}\n"; + } + + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string print_variantSetSpecStmt( + const std::map &vslist, + const uint32_t indent) { + std::stringstream ss; + + // ss << "# variantSet.size = " << std::to_string(vslist.size()) << "\n"; + for (const auto &variantSet : vslist) { + if (variantSet.second.variantSet.empty()) { + continue; + } + + ss << pprint::Indent(indent) << "variantSet " << quote(variantSet.first) + << " = {\n"; + + for (const auto &item : variantSet.second.variantSet) { + ss << pprint::Indent(indent + 1) << quote(item.first) << " "; + + if (item.second.metas().authored()) { + ss << "(\n"; + ss << print_prim_metas(item.second.metas(), indent + 2); + ss << pprint::Indent(indent + 1) << ") "; + } + + ss << "{\n"; + + // props + ss << print_props(item.second.props(), indent + 2); + + // primChildren + // TODO: print child Prims based on `primChildren` Prim metadata + const auto &variantPrimMetas = item.second.metas(); + const auto &variantPrimChildren = item.second.children(); + + if (variantPrimMetas.primChildren.size() == variantPrimChildren.size()) { + std::map primNameTable; + for (size_t i = 0; i < variantPrimChildren.size(); i++) { + primNameTable.emplace(variantPrimChildren[i].name(), + &variantPrimChildren[i]); + } + + for (size_t i = 0; i < variantPrimMetas.primChildren.size(); i++) { + value::token nameTok = variantPrimMetas.primChildren[i]; + DCOUT(fmt::format("variantPrimChildren {}/{} = {}", i, + variantPrimMetas.primChildren.size(), + nameTok.str())); + const auto it = primNameTable.find(nameTok.str()); + if (it != primNameTable.end()) { + ss << prim::print_primspec(*(it->second), indent + 2); + } else { + // TODO: Report warning? + } + } + } else { + for (const auto &child : variantPrimChildren) { + ss << prim::print_primspec(child, indent + 2); + } + } + + // ss << "# variantSet end\n"; + ss << pprint::Indent(indent + 1) << "}\n"; + } + + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const Model &model, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(model.spec); + if (model.prim_type_name.size()) { + ss << " " << model.prim_type_name; + } + ss << " \"" << model.name << "\"\n"; + + if (model.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(model.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + std::set tokset; + ss << print_props(model.props, tokset, model.propertyNames(), indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const Scope &scope, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(scope.spec) << " Scope \"" + << scope.name << "\"\n"; + if (scope.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(scope.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + std::set tokset; + ss << print_props(scope.props, tokset, scope.propertyNames(), indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const GPrim &gprim, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(gprim.spec) << " GPrim \"" + << gprim.name << "\"\n"; + ss << pprint::Indent(indent) << "(\n"; + // args + ss << pprint::Indent(indent) << ")\n"; + ss << pprint::Indent(indent) << "{\n"; + + ss << print_gprim_predefined(gprim, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const Xform &xform, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(xform.spec) << " Xform \"" + << xform.name << "\"\n"; + if (xform.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(xform.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + ss << print_gprim_predefined(xform, indent + 1); + + ss << print_props(xform.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const GeomCamera &camera, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(camera.spec) << " Camera \"" + << camera.name << "\"\n"; + if (camera.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(camera.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + // members + ss << print_typed_attr(camera.clippingRange, "clippingRange", indent + 1); + ss << print_typed_attr(camera.clippingPlanes, "clippingPlanes", indent + 1); + ss << print_typed_attr(camera.focalLength, "focalLength", indent + 1); + ss << print_typed_attr(camera.horizontalAperture, "horizontalAperture", + indent + 1); + ss << print_typed_attr(camera.horizontalApertureOffset, + "horizontalApertureOffset", indent + 1); + ss << print_typed_attr(camera.verticalAperture, "verticalAperture", + indent + 1); + ss << print_typed_attr(camera.verticalApertureOffset, + "verticalApertureOffset", indent + 1); + + ss << print_typed_token_attr(camera.projection, "projection", indent + 1); + ss << print_typed_token_attr(camera.stereoRole, "stereoRole", indent + 1); + + ss << print_typed_attr(camera.shutterOpen, "shutter:open", indent + 1); + ss << print_typed_attr(camera.shutterClose, "shutter:close", indent + 1); + + ss << print_gprim_predefined(camera, indent + 1); + + ss << print_props(camera.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +#if 0 +#define PRINT_TYPED_ATTR(__table, __propName, __var, __name, __indent) \ + if (__propName == __name) { \ + ss << print_typed_attr(__var, __name, __indent); \ + __table.insert(__name); \ + continue; \ + } +#endif + +std::string to_string(const GeomSphere &sphere, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(sphere.spec) << " Sphere \"" + << sphere.name << "\"\n"; + if (sphere.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(sphere.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + std::set table; + +#if 0 // TODO + if (sphere.propertyNames().size()) { + // pxrUSD sorts property, so does TinyUSDZ also. + std::vector sortedPropertyNames; + for (size_t i = 0; i < sphere.propertyNames().size(); i++) { + sortedPropertyNames.push_back(sphere.propertyNames()[i].str()); + } + std::sort(sortedPropertyNames.begin(), sortedPropertyNames.end()); + + for (size_t i = 0; i < sortedPropertyNames.size(); i++) { + std::string propName = sortedPropertyNames[i]; + + PRINT_TYPED_ATTR(table, propName, sphere.radius, "radius", indent + 1) + + if (emit_gprim_predefined(ss, &sphere, propName, indent + 1, table)) { + continue; + } + if (sphere.props.count(propName)) { + ss << print_prop(sphere.props.at(propName), propName, indent + 1); + table.insert(propName); + continue; + } + + // not found + ss << fmt::format( + "# Property `{}` is described in `properties` Prim metadatum, but " + "not found in this Prim. Possibly USDC file is corrupted.\n"); + } + } else { + // members + ss << print_typed_attr(sphere.radius, "radius", indent + 1); + + ss << print_gprim_predefined(sphere, indent + 1); + + ss << print_props(sphere.props, indent + 1); + } +#else + + ss << print_typed_attr(sphere.radius, "radius", indent + 1); + + ss << print_gprim_predefined(sphere, indent + 1); + + ss << print_props(sphere.props, indent + 1); + +#endif + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const GeomMesh &mesh, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(mesh.spec) << " Mesh \"" + << mesh.name << "\"\n"; + if (mesh.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(mesh.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + // members + ss << print_typed_attr(mesh.points, "points", indent + 1); + ss << print_typed_attr(mesh.normals, "normals", indent + 1); + ss << print_typed_attr(mesh.faceVertexIndices, "faceVertexIndices", + indent + 1); + ss << print_typed_attr(mesh.faceVertexCounts, "faceVertexCounts", indent + 1); + + if (mesh.skeleton) { + ss << print_relationship(mesh.skeleton.value(), + mesh.skeleton.value().get_listedit_qual(), + /* custom */ false, "skel:skeketon", indent + 1); + } + + ss << print_typed_attr(mesh.blendShapes, "skel:blendShapes", indent + 1); + if (mesh.blendShapeTargets) { + ss << print_relationship(mesh.blendShapeTargets.value(), + mesh.blendShapeTargets.value().get_listedit_qual(), + /* custom */ false, "skel:blendShapeTargets", + indent + 1); + } + + for (const auto &item : mesh.subsetFamilyTypeMap) { + std::string attr_name = "subsetFamily:" + item.first.str() + ":familyType"; + // TODO: Support connection attr? + ss << pprint::Indent(indent+1) << "uniform token " << attr_name << " = " << quote(to_string(item.second)) << "\n"; + } + + // subdiv + ss << print_typed_attr(mesh.cornerIndices, "cornerIndices", indent + 1); + ss << print_typed_attr(mesh.cornerSharpnesses, "cornerSharpnesses", + indent + 1); + ss << print_typed_attr(mesh.creaseIndices, "creaseIndices", indent + 1); + ss << print_typed_attr(mesh.creaseLengths, "creaseLengths", indent + 1); + ss << print_typed_attr(mesh.creaseSharpnesses, "creaseSharpnesses", + indent + 1); + ss << print_typed_attr(mesh.holeIndices, "holeIndices", indent + 1); + + ss << print_typed_token_attr(mesh.subdivisionScheme, "subdivisonScheme", + indent + 1); + ss << print_typed_token_attr(mesh.interpolateBoundary, "interpolateBoundary", + indent + 1); + ss << print_typed_token_attr(mesh.faceVaryingLinearInterpolation, + "faceVaryingLinearInterpolation", indent + 1); + + ss << print_gprim_predefined(mesh, indent + 1); + +#if 0 + // GeomSubset. + for (const auto &subset : mesh.geom_subset_children) { + ss << to_string(subset, indent + 1, /* closing_brace */ true); + } +#endif + + ss << print_props(mesh.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const GeomSubset &subset, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(subset.spec) << " GeomSubset \"" + << subset.name << "\"\n"; + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(subset.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + ss << pprint::Indent(indent) << "{\n"; + + ss << print_typed_token_attr(subset.elementType, "elementType", indent + 1); + ss << print_typed_attr(subset.familyName, "familyName", indent + 1); + ss << print_typed_attr(subset.indices, "indices", indent + 1); + + ss << print_material_binding(&subset, indent + 1); + ss << print_collection(&subset, indent + 1); + + ss << print_props(subset.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const GeomPoints &geom, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(geom.spec) << " Points \"" + << geom.name << "\"\n"; + if (geom.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(geom.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + // members + ss << print_typed_attr(geom.points, "points", indent + 1); + ss << print_typed_attr(geom.normals, "normals", indent + 1); + ss << print_typed_attr(geom.widths, "widths", indent + 1); + ss << print_typed_attr(geom.ids, "ids", indent + 1); + ss << print_typed_attr(geom.velocities, "velocities", indent + 1); + ss << print_typed_attr(geom.accelerations, "accelerations", indent + 1); + + ss << print_gprim_predefined(geom, indent + 1); + + ss << print_props(geom.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const GeomBasisCurves::Type &ty) { + std::string s; + + switch (ty) { + case GeomBasisCurves::Type::Cubic: { + s = "cubic"; + break; + } + case GeomBasisCurves::Type::Linear: { + s = "linear"; + break; + } + } + + return s; +} + +std::string to_string(const GeomBasisCurves::Basis &ty) { + std::string s; + + switch (ty) { + case GeomBasisCurves::Basis::Bezier: { + s = "bezier"; + break; + } + case GeomBasisCurves::Basis::Bspline: { + s = "bspline"; + break; + } + case GeomBasisCurves::Basis::CatmullRom: { + s = "catmullRom"; + break; + } + } + + return s; +} + +std::string to_string(const GeomBasisCurves::Wrap &ty) { + std::string s; + + switch (ty) { + case GeomBasisCurves::Wrap::Nonperiodic: { + s = "nonperiodic"; + break; + } + case GeomBasisCurves::Wrap::Periodic: { + s = "periodic"; + break; + } + case GeomBasisCurves::Wrap::Pinned: { + s = "pinned"; + break; + } + } + + return s; +} + +std::string to_string(const GeomBasisCurves &geom, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(geom.spec) << " BasisCurves \"" + << geom.name << "\"\n"; + if (geom.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(geom.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + // members + ss << print_typed_token_attr(geom.type, "type", indent + 1); + ss << print_typed_token_attr(geom.basis, "basis", indent + 1); + ss << print_typed_token_attr(geom.wrap, "wrap", indent + 1); + + ss << print_typed_attr(geom.points, "points", indent + 1); + ss << print_typed_attr(geom.normals, "normals", indent + 1); + ss << print_typed_attr(geom.widths, "widths", indent + 1); + ss << print_typed_attr(geom.velocities, "velocites", indent + 1); + ss << print_typed_attr(geom.accelerations, "accelerations", indent + 1); + ss << print_typed_attr(geom.curveVertexCounts, "curveVertexCounts", + indent + 1); + + ss << print_gprim_predefined(geom, indent + 1); + + ss << print_props(geom.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const GeomNurbsCurves &geom, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(geom.spec) << " NurbsCurves \"" + << geom.name << "\"\n"; + if (geom.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(geom.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + // members + ss << print_typed_attr(geom.points, "points", indent + 1); + ss << print_typed_attr(geom.normals, "normals", indent + 1); + ss << print_typed_attr(geom.widths, "widths", indent + 1); + ss << print_typed_attr(geom.velocities, "velocites", indent + 1); + ss << print_typed_attr(geom.accelerations, "accelerations", indent + 1); + ss << print_typed_attr(geom.curveVertexCounts, "curveVertexCounts", + indent + 1); + + // + ss << print_typed_attr(geom.order, "order", indent + 1); + ss << print_typed_attr(geom.knots, "knots", indent + 1); + ss << print_typed_attr(geom.ranges, "ranges", indent + 1); + ss << print_typed_attr(geom.pointWeights, "pointWeights", indent + 1); + + ss << print_gprim_predefined(geom, indent + 1); + + ss << print_props(geom.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const GeomCube &geom, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(geom.spec) << " Cube \"" + << geom.name << "\"\n"; + if (geom.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(geom.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + // members + ss << print_typed_attr(geom.size, "size", indent + 1); + + ss << print_gprim_predefined(geom, indent + 1); + + ss << print_props(geom.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const GeomCone &geom, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(geom.spec) << " Cone \"" + << geom.name << "\"\n"; + if (geom.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(geom.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + // members + ss << print_typed_attr(geom.radius, "radius", indent + 1); + ss << print_typed_attr(geom.height, "height", indent + 1); + + if (geom.axis.authored()) { + std::string axis; + if (geom.axis.get_value() == Axis::X) { + axis = "\"X\""; + } else if (geom.axis.get_value() == Axis::Y) { + axis = "\"Y\""; + } else { + axis = "\"Z\""; + } + ss << pprint::Indent(indent + 1) << "uniform token axis = " << axis << "\n"; + } + + ss << print_gprim_predefined(geom, indent + 1); + ss << print_props(geom.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const GeomCylinder &geom, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(geom.spec) << " Cylinder \"" + << geom.name << "\"\n"; + if (geom.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(geom.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + // members + ss << print_typed_attr(geom.radius, "radius", indent + 1); + ss << print_typed_attr(geom.height, "height", indent + 1); + + if (geom.axis.authored()) { + std::string axis; + if (geom.axis.get_value() == Axis::X) { + axis = "\"X\""; + } else if (geom.axis.get_value() == Axis::Y) { + axis = "\"Y\""; + } else { + axis = "\"Z\""; + } + ss << pprint::Indent(indent + 1) << "uniform token axis = " << axis << "\n"; + } + + ss << print_gprim_predefined(geom, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const GeomCapsule &geom, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(geom.spec) << " Capsule \"" + << geom.name << "\"\n"; + if (geom.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(geom.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + // members + ss << print_typed_attr(geom.radius, "radius", indent + 1); + ss << print_typed_attr(geom.height, "height", indent + 1); + + if (geom.axis.authored()) { + std::string axis; + if (geom.axis.get_value() == Axis::X) { + axis = "\"X\""; + } else if (geom.axis.get_value() == Axis::Y) { + axis = "\"Y\""; + } else { + axis = "\"Z\""; + } + ss << pprint::Indent(indent + 1) << "uniform token axis = " << axis << "\n"; + } + + ss << print_gprim_predefined(geom, indent + 1); + ss << print_props(geom.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const PointInstancer &instancer, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(instancer.spec) + << " PointInstancer \"" << instancer.name << "\"\n"; + if (instancer.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(instancer.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + // members + if (instancer.prototypes) { + ss << print_relationship(instancer.prototypes.value(), + instancer.prototypes.value().get_listedit_qual(), + /* custom */ false, "prototypes", indent + 1); + } + ss << print_typed_attr(instancer.protoIndices, "protoIndices", indent + 1); + ss << print_typed_attr(instancer.ids, "ids", indent + 1); + ss << print_typed_attr(instancer.invisibleIds, "invisibleIds", indent + 1); + ss << print_typed_attr(instancer.positions, "positions", indent + 1); + ss << print_typed_attr(instancer.orientations, "orientations", indent + 1); + ss << print_typed_attr(instancer.scales, "scales", indent + 1); + ss << print_typed_attr(instancer.velocities, "velocities", indent + 1); + ss << print_typed_attr(instancer.accelerations, "accelerations", indent + 1); + ss << print_typed_attr(instancer.angularVelocities, "angularVelocities", + indent + 1); + + ss << print_gprim_predefined(instancer, indent + 1); + + ss << print_props(instancer.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const SkelRoot &root, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(root.spec) << " SkelRoot \"" + << root.name << "\"\n"; + if (root.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(root.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + ss << print_typed_token_attr(root.visibility, "visibility", indent + 1); + ss << print_typed_token_attr(root.purpose, "purpose", indent + 1); + ss << print_typed_attr(root.extent, "extent", indent + 1); + + if (root.proxyPrim) { + ss << print_relationship(root.proxyPrim.value(), + root.proxyPrim.value().get_listedit_qual(), + /* custom */ false, "proxyPrim", indent + 1); + } + + // TODO + // Skeleton id + // ss << pprint::Indent(indent) << "skelroot.skeleton_id << "\n" + + ss << print_xformOps(root.xformOps, indent + 1); + + ss << print_props(root.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const Skeleton &skel, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(skel.spec) << " Skeleton \"" + << skel.name << "\"\n"; + if (skel.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(skel.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + ss << print_typed_attr(skel.bindTransforms, "bindTransforms", indent + 1); + ss << print_typed_attr(skel.jointNames, "jointNames", indent + 1); + ss << print_typed_attr(skel.joints, "joints", indent + 1); + ss << print_typed_attr(skel.restTransforms, "restTransforms", indent + 1); + + if (skel.animationSource) { + ss << print_relationship(skel.animationSource.value(), + skel.animationSource.value().get_listedit_qual(), + /* custom */ false, "skel:animationSource", + indent + 1); + } + + if (skel.proxyPrim) { + ss << print_relationship(skel.proxyPrim.value(), + skel.proxyPrim.value().get_listedit_qual(), + /* custom */ false, "proxyPrim", indent + 1); + } + + ss << print_xformOps(skel.xformOps, indent + 1); + + ss << print_typed_token_attr(skel.visibility, "visibility", indent + 1); + ss << print_typed_token_attr(skel.purpose, "purpose", indent + 1); + ss << print_typed_attr(skel.extent, "extent", indent + 1); + + ss << print_props(skel.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const SkelAnimation &skelanim, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(skelanim.spec) + << " SkelAnimation \"" << skelanim.name << "\"\n"; + if (skelanim.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(skelanim.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + ss << print_typed_attr(skelanim.blendShapes, "blendShapes", indent + 1); + ss << print_typed_attr(skelanim.blendShapeWeights, "blendShapeWeights", + indent + 1); + ss << print_typed_attr(skelanim.joints, "joints", indent + 1); + ss << print_typed_attr(skelanim.rotations, "rotations", indent + 1); + ss << print_typed_attr(skelanim.scales, "scales", indent + 1); + ss << print_typed_attr(skelanim.translations, "translations", indent + 1); + + ss << print_props(skelanim.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const BlendShape &prim, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(prim.spec) << " BlendShape \"" + << prim.name << "\"\n"; + if (prim.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(prim.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + ss << print_typed_attr(prim.offsets, "offsets", indent + 1); + ss << print_typed_attr(prim.normalOffsets, "normalOffsets", indent + 1); + ss << print_typed_attr(prim.pointIndices, "pointIndices", indent + 1); + + ss << print_props(prim.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const Material &material, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(material.spec) << " Material \"" + << material.name << "\"\n"; + if (material.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(material.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + if (material.surface.authored()) { + // assume connection when authored. + // TODO: list edit?. + ss << pprint::Indent(indent + 1) << "token outputs:surface.connect "; + + const auto &conns = material.surface.get_connections(); + if (conns.size() == 1) { + ss << "= " << pquote(conns[0]); + } else if (conns.size() > 1) { + ss << "= ["; + for (size_t i = 0; i < conns.size(); i++) { + ss << pquote(conns[i]); + if (i != (conns.size() - 1)) { + ss << ", "; + } + } + ss << "]"; + } + + if (material.surface.metas().authored()) { + ss << "(\n" + << print_attr_metas(material.surface.metas(), indent + 2) + << pprint::Indent(indent + 1) << ")"; + } + ss << "\n"; + } + + if (material.displacement.authored()) { + // assume connection when authored. + // TODO: list edit?. + ss << pprint::Indent(indent + 1) << "token outputs:displacement.connect "; + + const auto &conns = material.displacement.get_connections(); + if (conns.size() == 1) { + ss << "= " << pquote(conns[0]); + } else if (conns.size() > 1) { + ss << "= ["; + for (size_t i = 0; i < conns.size(); i++) { + ss << pquote(conns[i]); + if (i != (conns.size() - 1)) { + ss << ", "; + } + } + ss << "]"; + } + + if (material.displacement.metas().authored()) { + ss << "(\n" + << print_attr_metas(material.displacement.metas(), indent + 2) + << pprint::Indent(indent + 1) << ")"; + } + ss << "\n"; + } + + if (material.volume.authored()) { + // assume connection when authored. + // TODO: list edit?. + ss << pprint::Indent(indent + 1) << "token outputs:volume.connect "; + + const auto &conns = material.volume.get_connections(); + if (conns.size() == 1) { + ss << "= " << pquote(conns[0]); + } else if (conns.size() > 1) { + ss << "= ["; + for (size_t i = 0; i < conns.size(); i++) { + ss << pquote(conns[i]); + if (i != (conns.size() - 1)) { + ss << ", "; + } + } + ss << "]"; + } + + if (material.volume.metas().authored()) { + ss << "(\n" + << print_attr_metas(material.volume.metas(), indent + 2) + << pprint::Indent(indent + 1) << ")"; + } + ss << "\n"; + } + + ss << print_props(material.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +static std::string print_common_shader_params(const ShaderNode &shader, + const uint32_t indent) { + std::stringstream ss; + + ss << print_props(shader.props, indent); + + return ss.str(); +} + +static std::string print_shader_params(const UsdPrimvarReader_float &shader, + const uint32_t indent) { + std::stringstream ss; + + ss << print_str_attr(shader.varname, "inputs:varname", indent); + ss << print_typed_attr(shader.fallback, "inputs:fallback", indent); + ss << print_typed_terminal_attr(shader.result, "outputs:result", indent); + + ss << print_common_shader_params(shader, indent); + + return ss.str(); +} + +static std::string print_shader_params(const UsdPrimvarReader_float2 &shader, + const uint32_t indent) { + std::stringstream ss; + + ss << print_str_attr(shader.varname, "inputs:varname", indent); + ss << print_typed_attr(shader.fallback, "inputs:fallback", indent); + ss << print_typed_terminal_attr(shader.result, "outputs:result", indent); + + ss << print_common_shader_params(shader, indent); + + return ss.str(); +} + +static std::string print_shader_params(const UsdPrimvarReader_float3 &shader, + const uint32_t indent) { + std::stringstream ss; + + ss << print_str_attr(shader.varname, "inputs:varname", indent); + ss << print_typed_attr(shader.fallback, "inputs:fallback", indent); + ss << print_typed_terminal_attr(shader.result, "outputs:result", indent); + + ss << print_common_shader_params(shader, indent); + + return ss.str(); +} + +static std::string print_shader_params(const UsdPrimvarReader_float4 &shader, + const uint32_t indent) { + std::stringstream ss; + + ss << print_str_attr(shader.varname, "inputs:varname", indent); + ss << print_typed_attr(shader.fallback, "inputs:fallback", indent); + ss << print_typed_terminal_attr(shader.result, "outputs:result", indent); + + ss << print_common_shader_params(shader, indent); + + return ss.str(); +} + +static std::string print_shader_params(const UsdPrimvarReader_string &shader, + const uint32_t indent) { + std::stringstream ss; + + ss << print_str_attr(shader.varname, "inputs:varname", indent); + ss << print_typed_attr(shader.fallback, "inputs:fallback", indent); + ss << print_typed_terminal_attr(shader.result, "outputs:result", indent); + + ss << print_common_shader_params(shader, indent); + + return ss.str(); +} + +static std::string print_shader_params(const UsdPrimvarReader_normal &shader, + const uint32_t indent) { + std::stringstream ss; + + ss << print_str_attr(shader.varname, "inputs:varname", indent); + ss << print_typed_attr(shader.fallback, "inputs:fallback", indent); + ss << print_typed_terminal_attr(shader.result, "outputs:result", indent); + + ss << print_common_shader_params(shader, indent); + + return ss.str(); +} + +static std::string print_shader_params(const UsdPrimvarReader_vector &shader, + const uint32_t indent) { + std::stringstream ss; + + ss << print_str_attr(shader.varname, "inputs:varname", indent); + ss << print_typed_attr(shader.fallback, "inputs:fallback", indent); + ss << print_typed_terminal_attr(shader.result, "outputs:result", indent); + + ss << print_common_shader_params(shader, indent); + + return ss.str(); +} + +static std::string print_shader_params(const UsdPrimvarReader_point &shader, + const uint32_t indent) { + std::stringstream ss; + + ss << print_str_attr(shader.varname, "inputs:varname", indent); + ss << print_typed_attr(shader.fallback, "inputs:fallback", indent); + ss << print_typed_terminal_attr(shader.result, "outputs:result", indent); + + ss << print_common_shader_params(shader, indent); + + return ss.str(); +} + +static std::string print_shader_params(const UsdPrimvarReader_matrix &shader, + const uint32_t indent) { + std::stringstream ss; + + ss << print_str_attr(shader.varname, "inputs:varname", indent); + ss << print_typed_attr(shader.fallback, "inputs:fallback", indent); + ss << print_typed_terminal_attr(shader.result, "outputs:result", indent); + + ss << print_common_shader_params(shader, indent); + + return ss.str(); +} + +static std::string print_shader_params(const UsdTransform2d &shader, + const uint32_t indent) { + std::stringstream ss; + + ss << print_typed_attr(shader.in, "inputs:in", indent); + ss << print_typed_attr(shader.rotation, "inputs:rotation", indent); + ss << print_typed_attr(shader.scale, "inputs:scale", indent); + ss << print_typed_attr(shader.translation, "inputs:translation", indent); + ss << print_typed_terminal_attr(shader.result, "outputs:result", indent); + + ss << print_common_shader_params(shader, indent); + + return ss.str(); +} + +static std::string print_shader_params(const UsdPreviewSurface &shader, + const uint32_t indent) { + std::stringstream ss; + + ss << print_typed_attr(shader.diffuseColor, "inputs:diffuseColor", indent); + ss << print_typed_attr(shader.emissiveColor, "inputs:emissiveColor", indent); + ss << print_typed_attr(shader.useSpecularWorkflow, + "inputs:useSpecularWorkflow", indent); + ss << print_typed_attr(shader.ior, "inputs:ior", indent); + ss << print_typed_attr(shader.specularColor, "inputs:specularColor", indent); + ss << print_typed_attr(shader.metallic, "inputs:metallic", indent); + ss << print_typed_attr(shader.clearcoat, "inputs:clearcoat", indent); + ss << print_typed_attr(shader.clearcoatRoughness, "inputs:clearcoatRoughness", + indent); + ss << print_typed_attr(shader.roughness, "inputs:roughness", indent); + ss << print_typed_attr(shader.opacity, "inputs:opacity", indent); + ss << print_typed_attr(shader.opacityThreshold, "inputs:opacityThreshold", + indent); + ss << print_typed_attr(shader.normal, "inputs:normal", indent); + ss << print_typed_attr(shader.displacement, "inputs:displacement", indent); + ss << print_typed_attr(shader.occlusion, "inputs:occlusion", indent); + + ss << print_typed_terminal_attr(shader.outputsSurface, "outputs:surface", + indent); + ss << print_typed_terminal_attr(shader.outputsDisplacement, + "outputs:displacement", indent); + + ss << print_common_shader_params(shader, indent); + + return ss.str(); +} + +static std::string print_shader_params(const UsdUVTexture &shader, + const uint32_t indent) { + std::stringstream ss; + + ss << print_typed_attr(shader.file, "inputs:file", indent); + + ss << print_typed_token_attr(shader.sourceColorSpace, + "inputs:sourceColorSpace", indent); + + ss << print_typed_attr(shader.fallback, "inputs:fallback", indent); + + ss << print_typed_attr(shader.bias, "inputs:bias", indent); + ss << print_typed_attr(shader.scale, "inputs:scale", indent); + + ss << print_typed_attr(shader.st, "inputs:st", indent); + ss << print_typed_token_attr(shader.wrapS, "inputs:wrapT", indent); + ss << print_typed_token_attr(shader.wrapT, "inputs:wrapS", indent); + + ss << print_typed_terminal_attr(shader.outputsR, "outputs:r", indent); + ss << print_typed_terminal_attr(shader.outputsG, "outputs:g", indent); + ss << print_typed_terminal_attr(shader.outputsB, "outputs:b", indent); + ss << print_typed_terminal_attr(shader.outputsA, "outputs:a", indent); + ss << print_typed_terminal_attr(shader.outputsRGB, "outputs:rgb", indent); + + ss << print_common_shader_params(shader, indent); + + return ss.str(); +} + +std::string to_string(const Shader &shader, const uint32_t indent, + bool closing_brace) { + // generic Shader class + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(shader.spec) << " Shader \"" + << shader.name << "\"\n"; + if (shader.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(shader.metas(), indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + // members + if (shader.info_id.size()) { + ss << pprint::Indent(indent + 1) << "uniform token info:id = \"" + << shader.info_id << "\"\n"; + } + + if (auto pvr = shader.value.get_value()) { + ss << print_shader_params(pvr.value(), indent + 1); + } else if (auto pvr2 = shader.value.get_value()) { + ss << print_shader_params(pvr2.value(), indent + 1); + } else if (auto pvr3 = shader.value.get_value()) { + ss << print_shader_params(pvr3.value(), indent + 1); + } else if (auto pvr4 = shader.value.get_value()) { + ss << print_shader_params(pvr4.value(), indent + 1); + } else if (auto pvrs = shader.value.get_value()) { + ss << print_shader_params(pvrs.value(), indent + 1); + } else if (auto pvrn = shader.value.get_value()) { + ss << print_shader_params(pvrn.value(), indent + 1); + } else if (auto pvrv = shader.value.get_value()) { + ss << print_shader_params(pvrv.value(), indent + 1); + } else if (auto pvrp = shader.value.get_value()) { + ss << print_shader_params(pvrp.value(), indent + 1); + } else if (auto pvrm = shader.value.get_value()) { + ss << print_shader_params(pvrm.value(), indent + 1); + } else if (auto pvtex = shader.value.get_value()) { + ss << print_shader_params(pvtex.value(), indent + 1); + } else if (auto pvtx2d = shader.value.get_value()) { + ss << print_shader_params(pvtx2d.value(), indent + 1); + } else if (auto pvs = shader.value.get_value()) { + ss << print_shader_params(pvs.value(), indent + 1); + } else if (auto pvsn = shader.value.get_value()) { + // Generic ShaderNode + ss << print_common_shader_params(pvsn.value(), indent + 1); + } else { + ss << pprint::Indent(indent + 1) + << "[???] Invalid ShaderNode in Shader Prim\n"; + } + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const UsdPreviewSurface &surf, const uint32_t indent, + bool closing_brace) { + // TODO: Print spec and meta? + std::stringstream ss; + + ss << pprint::Indent(indent) << "{\n"; + ss << print_shader_params(surf, indent); + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const UsdUVTexture &tex, const uint32_t indent, + bool closing_brace) { + // TODO: Print spec and meta? + std::stringstream ss; + + ss << pprint::Indent(indent) << "{\n"; + ss << print_shader_params(tex, indent); + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const UsdPrimvarReader_float2 &preader, + const uint32_t indent, bool closing_brace) { + // TODO: Print spec and meta? + std::stringstream ss; + + ss << pprint::Indent(indent) << "{\n"; + ss << print_shader_params(preader, indent); + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const SphereLight &light, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(light.spec) << " SphereLight \"" + << light.name << "\"\n"; + if (light.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(light.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + // members + ss << print_typed_attr(light.color, "inputs:color", indent + 1); + ss << print_typed_attr(light.colorTemperature, "inputs:colorTemperature", + indent + 1); + ss << print_typed_attr(light.diffuse, "inputs:diffuse", indent + 1); + ss << print_typed_attr(light.enableColorTemperature, + "inputs:enableColorTemperature", indent + 1); + ss << print_typed_attr(light.exposure, "inputs:exposure", indent + 1); + ss << print_typed_attr(light.intensity, "inputs:intensity", indent + 1); + ss << print_typed_attr(light.normalize, "inputs:normalize", indent + 1); + ss << print_typed_attr(light.specular, "inputs:specular", indent + 1); + + ss << print_typed_attr(light.radius, "inputs:radius", indent + 1); + + ss << print_typed_attr(light.extent, "extent", indent + 1); + ss << print_typed_token_attr(light.visibility, "visibility", indent + 1); + ss << print_typed_token_attr(light.purpose, "purpose", indent + 1); + + ss << print_xformOps(light.xformOps, indent + 1); + ss << print_props(light.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const DistantLight &light, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(light.spec) << " DistantLight \"" + << light.name << "\"\n"; + if (light.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(light.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + // members + ss << print_typed_attr(light.color, "inputs:color", indent + 1); + ss << print_typed_attr(light.colorTemperature, "inputs:colorTemperature", + indent + 1); + ss << print_typed_attr(light.diffuse, "inputs:diffuse", indent + 1); + ss << print_typed_attr(light.enableColorTemperature, + "inputs:enableColorTemperature", indent + 1); + ss << print_typed_attr(light.exposure, "inputs:exposure", indent + 1); + ss << print_typed_attr(light.intensity, "inputs:intensity", indent + 1); + ss << print_typed_attr(light.normalize, "inputs:normalize", indent + 1); + ss << print_typed_attr(light.specular, "inputs:specular", indent + 1); + + ss << print_typed_attr(light.angle, "inputs:angle", indent + 1); + + //ss << print_typed_attr(light.extent, "extent", indent + 1); + ss << print_typed_token_attr(light.visibility, "visibility", indent + 1); + ss << print_typed_token_attr(light.purpose, "purpose", indent + 1); + + ss << print_xformOps(light.xformOps, indent + 1); + ss << print_props(light.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const CylinderLight &light, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(light.spec) << " CylinderLight \"" + << light.name << "\"\n"; + if (light.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(light.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + // members + ss << print_typed_attr(light.color, "inputs:color", indent + 1); + ss << print_typed_attr(light.colorTemperature, "inputs:colorTemperature", + indent + 1); + ss << print_typed_attr(light.diffuse, "inputs:diffuse", indent + 1); + ss << print_typed_attr(light.enableColorTemperature, + "inputs:enableColorTemperature", indent + 1); + ss << print_typed_attr(light.exposure, "inputs:exposure", indent + 1); + ss << print_typed_attr(light.intensity, "inputs:intensity", indent + 1); + ss << print_typed_attr(light.normalize, "inputs:normalize", indent + 1); + ss << print_typed_attr(light.specular, "inputs:specular", indent + 1); + + ss << print_typed_attr(light.length, "inputs:length", indent + 1); + ss << print_typed_attr(light.radius, "inputs:radius", indent + 1); + + ss << print_typed_attr(light.extent, "extent", indent + 1); + ss << print_typed_token_attr(light.visibility, "visibility", indent + 1); + ss << print_typed_token_attr(light.purpose, "purpose", indent + 1); + + ss << print_xformOps(light.xformOps, indent + 1); + ss << print_props(light.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const DiskLight &light, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(light.spec) << " DiskLight \"" + << light.name << "\"\n"; + if (light.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(light.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + // members + ss << print_typed_attr(light.color, "inputs:color", indent + 1); + ss << print_typed_attr(light.colorTemperature, "inputs:colorTemperature", + indent + 1); + ss << print_typed_attr(light.diffuse, "inputs:diffuse", indent + 1); + ss << print_typed_attr(light.enableColorTemperature, + "inputs:enableColorTemperature", indent + 1); + ss << print_typed_attr(light.exposure, "inputs:exposure", indent + 1); + ss << print_typed_attr(light.intensity, "inputs:intensity", indent + 1); + ss << print_typed_attr(light.normalize, "inputs:normalize", indent + 1); + ss << print_typed_attr(light.specular, "inputs:specular", indent + 1); + + ss << print_typed_attr(light.radius, "inputs:radius", indent + 1); + + ss << print_typed_attr(light.extent, "extent", indent + 1); + ss << print_typed_token_attr(light.visibility, "visibility", indent + 1); + ss << print_typed_token_attr(light.purpose, "purpose", indent + 1); + + ss << print_xformOps(light.xformOps, indent + 1); + ss << print_props(light.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const DomeLight &light, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(light.spec) << " DomeLight \"" + << light.name << "\"\n"; + if (light.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(light.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + // members + ss << print_typed_attr(light.color, "inputs:color", indent + 1); + ss << print_typed_attr(light.colorTemperature, "inputs:colorTemperature", + indent + 1); + ss << print_typed_attr(light.diffuse, "inputs:diffuse", indent + 1); + ss << print_typed_attr(light.enableColorTemperature, + "inputs:enableColorTemperature", indent + 1); + ss << print_typed_attr(light.exposure, "inputs:exposure", indent + 1); + ss << print_typed_attr(light.intensity, "inputs:intensity", indent + 1); + ss << print_typed_attr(light.normalize, "inputs:normalize", indent + 1); + ss << print_typed_attr(light.specular, "inputs:specular", indent + 1); + + ss << print_typed_attr(light.guideRadius, "inputs:guideRadius", indent + 1); + ss << print_typed_attr(light.file, "inputs:file", indent + 1); + ss << print_typed_token_attr(light.textureFormat, "inputs:textureFormat", + indent + 1); + + //ss << print_typed_attr(light.extent, "extent", indent + 1); + ss << print_typed_token_attr(light.visibility, "visibility", indent + 1); + ss << print_typed_token_attr(light.purpose, "purpose", indent + 1); + + ss << print_xformOps(light.xformOps, indent + 1); + + ss << print_props(light.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const RectLight &light, const uint32_t indent, + bool closing_brace) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(light.spec) << " RectLight \"" + << light.name << "\"\n"; + if (light.meta.authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(light.meta, indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + // members + ss << print_typed_attr(light.color, "inputs:color", indent + 1); + ss << print_typed_attr(light.colorTemperature, "inputs:colorTemperature", + indent + 1); + ss << print_typed_attr(light.diffuse, "inputs:diffuse", indent + 1); + ss << print_typed_attr(light.enableColorTemperature, + "inputs:enableColorTemperature", indent + 1); + ss << print_typed_attr(light.exposure, "inputs:exposure", indent + 1); + ss << print_typed_attr(light.intensity, "inputs:intensity", indent + 1); + ss << print_typed_attr(light.normalize, "inputs:normalize", indent + 1); + ss << print_typed_attr(light.specular, "inputs:specular", indent + 1); + + ss << print_typed_attr(light.file, "inputs:file", indent + 1); + ss << print_typed_attr(light.height, "inputs:height", indent + 1); + ss << print_typed_attr(light.width, "inputs:width", indent + 1); + ss << print_typed_attr(light.height, "inputs:height", indent + 1); + + ss << print_typed_attr(light.extent, "extent", indent + 1); + ss << print_typed_token_attr(light.visibility, "visibility", indent + 1); + ss << print_typed_token_attr(light.purpose, "purpose", indent + 1); + + ss << print_xformOps(light.xformOps, indent + 1); + ss << print_props(light.props, indent + 1); + + if (closing_brace) { + ss << pprint::Indent(indent) << "}\n"; + } + + return ss.str(); +} + +std::string to_string(const GeomCamera::Projection &proj) { + if (proj == GeomCamera::Projection::Orthographic) { + return "orthographic"; + } else { + return "perspective"; + } +} + +std::string to_string(const GeomCamera::StereoRole &role) { + if (role == GeomCamera::StereoRole::Mono) { + return "mono"; + } else if (role == GeomCamera::StereoRole::Right) { + return "right"; + } else { + return "left"; + } +} + +std::string to_string(const Path &path, bool show_full_path) { + if (show_full_path) { + return path.full_path_name(); + } else { + // TODO + return path.full_path_name(); + } +} + +std::string to_string(const std::vector &v, bool show_full_path) { + // TODO(syoyo): indent + std::stringstream ss; + ss << "["; + + for (size_t i = 0; i < v.size(); i++) { + ss << to_string(v[i], show_full_path); + if (i != (v.size() - 1)) { + ss << ", "; + } + } + ss << "]"; + return ss.str(); +} + +std::string to_string(const XformOp::OpType &op) { + std::string ss; + + switch (op) { + case XformOp::OpType::ResetXformStack: { + ss = "!resetXformStack!"; + break; + } + case XformOp::OpType::Transform: { + ss = "xformOp:transform"; + break; + } + case XformOp::OpType::Translate: { + ss = "xformOp:translate"; + break; + } + case XformOp::OpType::Scale: { + ss = "xformOp:scale"; + break; + } + case XformOp::OpType::RotateX: { + ss = "xformOp:rotateX"; + break; + } + case XformOp::OpType::RotateY: { + ss = "xformOp:rotateY"; + break; + } + case XformOp::OpType::RotateZ: { + ss = "xformOp:rotateZ"; + break; + } + case XformOp::OpType::RotateXYZ: { + ss = "xformOp:rotateXYZ"; + break; + } + case XformOp::OpType::RotateXZY: { + ss = "xformOp:rotateXZY"; + break; + } + case XformOp::OpType::RotateYXZ: { + ss = "xformOp:rotateYXZ"; + break; + } + case XformOp::OpType::RotateYZX: { + ss = "xformOp:rotateYZX"; + break; + } + case XformOp::OpType::RotateZXY: { + ss = "xformOp:rotateZXY"; + break; + } + case XformOp::OpType::RotateZYX: { + ss = "xformOp:rotateZYX"; + break; + } + case XformOp::OpType::Orient: { + ss = "xformOp:orient"; + break; + } + } + + return ss; +} + +std::string to_string(const tinyusdz::value::token &v) { return v.str(); } + +std::string to_string(const DomeLight::TextureFormat &texformat) { + std::string s = "[InvalidTextureFormat]"; + + switch (texformat) { + case DomeLight::TextureFormat::Automatic: { + s = "automatic"; + break; + } + case DomeLight::TextureFormat::Latlong: { + s = "latlong"; + break; + } + case DomeLight::TextureFormat::MirroredBall: { + s = "mirroedBall"; + break; + } + case DomeLight::TextureFormat::Angular: { + s = "angular"; + break; + } + } + + return s; +} + +std::string dump_path(const Path &path) { + std::stringstream ss; + ss << "Path: Prim part = " << path.prim_part(); + ss << ", Prop part = " << path.prop_part(); + ss << ", Variant part = " << path.variant_part(); + ss << ", elementName = " << path.element_name(); + ss << ", isValid = " << path.is_valid(); + ss << ", isAbsolute = " << path.is_absolute_path(); + ss << ", isRelative = " << path.is_relative_path(); + + return ss.str(); +} + +std::string print_layer_metas(const LayerMetas &metas, const uint32_t indent) { + std::stringstream meta_ss; + + if (metas.doc.value.empty()) { + // ss << pprint::Indent(1) << "doc = \"Exporterd from TinyUSDZ v" << + // tinyusdz::version_major + // << "." << tinyusdz::version_minor << "." << tinyusdz::version_micro + // << tinyusdz::version_rev << "\"\n"; + } else { + meta_ss << pprint::Indent(indent) << "doc = " << to_string(metas.doc) + << "\n"; + } + + if (metas.metersPerUnit.authored()) { + meta_ss << pprint::Indent(indent) + << "metersPerUnit = " << metas.metersPerUnit.get_value() << "\n"; + } + + if (metas.upAxis.authored()) { + meta_ss << pprint::Indent(indent) + << "upAxis = " << quote(to_string(metas.upAxis.get_value())) + << "\n"; + } + + if (metas.timeCodesPerSecond.authored()) { + meta_ss << pprint::Indent(indent) + << "timeCodesPerSecond = " << metas.timeCodesPerSecond.get_value() + << "\n"; + } + + if (metas.startTimeCode.authored()) { + meta_ss << pprint::Indent(indent) + << "startTimeCode = " << metas.startTimeCode.get_value() << "\n"; + } + + if (metas.endTimeCode.authored()) { + meta_ss << pprint::Indent(indent) + << "endTimeCode = " << metas.endTimeCode.get_value() << "\n"; + } + + if (metas.framesPerSecond.authored()) { + meta_ss << pprint::Indent(indent) + << "framesPerSecond = " << metas.framesPerSecond.get_value() + << "\n"; + } + + // TODO: Do not print subLayers when consumed(after composition evaluated) + if (metas.subLayers.size()) { + meta_ss << pprint::Indent(indent) << "subLayers = " << metas.subLayers + << "\n"; + } + + if (metas.defaultPrim.str().size()) { + meta_ss << pprint::Indent(1) + << "defaultPrim = " << tinyusdz::quote(metas.defaultPrim.str()) + << "\n"; + } + + if (metas.autoPlay.authored()) { + meta_ss << pprint::Indent(1) + << "autoPlay = " << to_string(metas.autoPlay.get_value()) << "\n"; + } + + if (metas.playbackMode.authored()) { + auto v = metas.playbackMode.get_value(); + if (v == LayerMetas::PlaybackMode::PlaybackModeLoop) { + meta_ss << pprint::Indent(indent) << "playbackMode = \"loop\"\n"; + } else { // None + meta_ss << pprint::Indent(indent) << "playbackMode = \"none\"\n"; + } + } + + if (!metas.comment.value.empty()) { + // Stage meta omits 'comment' + meta_ss << pprint::Indent(indent) << to_string(metas.comment) << "\n"; + } + + if (metas.customLayerData.size()) { + meta_ss << print_customData(metas.customLayerData, "customLayerData", + /* indent */ 1); + } + + return meta_ss.str(); +} + +std::string print_layer(const Layer &layer, const uint32_t indent) { + std::stringstream ss; + + // FIXME: print magic-header outside of this function? + ss << pprint::Indent(indent) << "#usda 1.0\n"; + + std::stringstream meta_ss; + meta_ss << print_layer_metas(layer.metas(), indent + 1); + + if (meta_ss.str().size()) { + ss << "(\n"; + ss << meta_ss.str(); + ss << ")\n"; + } + + ss << "\n"; + + if (layer.metas().primChildren.size() == layer.primspecs().size()) { + std::map primNameTable; + for (const auto &item : layer.primspecs()) { + primNameTable.emplace(item.first, &item.second); + } + + for (size_t i = 0; i < layer.metas().primChildren.size(); i++) { + value::token nameTok = layer.metas().primChildren[i]; + // DCOUT(fmt::format("primChildren {}/{} = {}", i, + // layer.metas().primChildren.size(), nameTok.str())); + const auto it = primNameTable.find(nameTok.str()); + if (it != primNameTable.end()) { + ss << prim::print_primspec((*it->second), indent); + if (i != (layer.metas().primChildren.size() - 1)) { + ss << "\n"; + } + } else { + // TODO: Report warning? + } + } + } else { + size_t i = 0; + for (const auto &item : layer.primspecs()) { + ss << prim::print_primspec(item.second, indent); + if (i != (layer.primspecs().size() - 1)) { + ss << "\n"; + } + } + } + + return ss.str(); +} + +// prim-pprint.hh +namespace prim { + +std::string print_prim(const Prim &prim, const uint32_t indent) { + std::stringstream ss; + + // Currently, Prim's elementName is read from name variable in concrete Prim + // class(e.g. Xform::name). + // TODO: use prim.elementPath for elementName. + std::string s = pprint_value(prim.data(), indent, /* closing_brace */ false); + + bool require_newline = true; + + // Check last 2 chars. + // if it ends with '{\n', no properties are authored so do not emit blank line + // before printing VariantSet or child Prims. + if (s.size() > 2) { + if ((s[s.size() - 2] == '{') && (s[s.size() - 1] == '\n')) { + require_newline = false; + } + } + + ss << s; + + // + // print variant + // + if (prim.variantSets().size()) { + if (require_newline) { + ss << "\n"; + } + + // need to add blank line after VariantSet stmt and before child Prims, + // so set require_newline true + require_newline = true; + + for (const auto &variantSet : prim.variantSets()) { + ss << pprint::Indent(indent + 1) << "variantSet " + << quote(variantSet.first) << " = {\n"; + + for (const auto &variantItem : variantSet.second.variantSet) { + ss << pprint::Indent(indent + 2) << quote(variantItem.first); + + const Variant &variant = variantItem.second; + + if (variant.metas().authored()) { + ss << " (\n"; + ss << print_prim_metas(variant.metas(), indent + 3); + ss << pprint::Indent(indent + 2) << ")"; + } + + ss << " {\n"; + + ss << print_props(variant.properties(), indent + 3); + + if (variant.metas().variantChildren.has_value() && + (variant.metas().variantChildren.value().size() == + variant.primChildren().size())) { + std::map primNameTable; + for (size_t i = 0; i < variant.primChildren().size(); i++) { + primNameTable.emplace(variant.primChildren()[i].element_name(), + &variant.primChildren()[i]); + } + + for (size_t i = 0; i < variant.metas().variantChildren.value().size(); + i++) { + value::token nameTok = variant.metas().variantChildren.value()[i]; + const auto it = primNameTable.find(nameTok.str()); + if (it != primNameTable.end()) { + ss << print_prim(*(it->second), indent + 3); + if (i != (variant.primChildren().size() - 1)) { + ss << "\n"; + } + } else { + // TODO: Report warning? + } + } + + } else { + for (size_t i = 0; i < variant.primChildren().size(); i++) { + ss << print_prim(variant.primChildren()[i], indent + 3); + if (i != (variant.primChildren().size() - 1)) { + ss << "\n"; + } + } + } + + ss << pprint::Indent(indent + 2) << "}\n"; + } + + ss << pprint::Indent(indent + 1) << "}\n"; + } + } + + // + // primChildren + // + if (prim.children().size()) { + if (require_newline) { + ss << "\n"; + require_newline = false; + } + if (prim.metas().primChildren.size() == prim.children().size()) { + // Use primChildren info to determine the order of the traversal. + + std::map primNameTable; + for (size_t i = 0; i < prim.children().size(); i++) { + primNameTable.emplace(prim.children()[i].element_name(), + &prim.children()[i]); + } + + for (size_t i = 0; i < prim.metas().primChildren.size(); i++) { + if (i > 0) { + ss << "\n"; + } + value::token nameTok = prim.metas().primChildren[i]; + DCOUT(fmt::format("primChildren {}/{} = {}", i, + prim.metas().primChildren.size(), nameTok.str())); + const auto it = primNameTable.find(nameTok.str()); + if (it != primNameTable.end()) { + ss << print_prim(*(it->second), indent + 1); + } else { + // TODO: Report warning? + } + } + + } else { + for (size_t i = 0; i < prim.children().size(); i++) { + if (i > 0) { + ss << "\n"; + } + ss << print_prim(prim.children()[i], indent + 1); + } + } + } + + ss << pprint::Indent(indent) << "}\n"; + + return ss.str(); +} + +std::string print_primspec(const PrimSpec &primspec, const uint32_t indent) { + std::stringstream ss; + + ss << pprint::Indent(indent) << to_string(primspec.specifier()) << " "; + if (primspec.typeName().empty() || primspec.typeName() == "Model") { + // do not emit typeName + } else { + ss << primspec.typeName() << " "; + } + + ss << "\"" << primspec.name() << "\"\n"; + + if (primspec.metas().authored()) { + ss << pprint::Indent(indent) << "(\n"; + ss << print_prim_metas(primspec.metas(), indent + 1); + ss << pprint::Indent(indent) << ")\n"; + } + ss << pprint::Indent(indent) << "{\n"; + + ss << print_props(primspec.props(), indent + 1); + + // TODO: print according to primChildren metadatum + for (size_t i = 0; i < primspec.children().size(); i++) { + if (i > 0) { + ss << pprint::Indent(indent) << "\n"; + } + ss << print_primspec(primspec.children()[i], indent + 1); + } + + // ss << "# variant \n"; + ss << print_variantSetSpecStmt(primspec.variantSets(), indent + 1); + + ss << pprint::Indent(indent) << "}\n"; + + return ss.str(); +} + +} // namespace prim + +std::string to_string(const Layer &layer, const uint32_t indent, + bool closing_brace) { + (void)closing_brace; + return print_layer(layer, indent); +} + +std::string to_string(const PrimSpec &primspec, const uint32_t indent, + bool closing_brace) { + (void)closing_brace; + return prim::print_primspec(primspec, indent); +} + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/pprinter.hh b/contrib/tinyusdz/tinyusdz_repo/src/pprinter.hh new file mode 100644 index 000000000..0a4ab3f31 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/pprinter.hh @@ -0,0 +1,284 @@ +#pragma once + +// +// pretty-print routine(using iostream) in non-intrusive way. +// Some build configuration may not want I/O module(e.g. mobile/embedded +// device), so provide print routines in separated file. +// +// + +#include +#include +#include + +#include "prim-types.hh" +#include "usdGeom.hh" +#include "usdLux.hh" +#include "usdShade.hh" +#include "usdSkel.hh" +#include "value-pprint.hh" + +namespace tinyusdz { + +namespace pprint { + +void SetIndentString(const std::string &s); +std::string Indent(uint32_t level); + +} // namespace pprint + +std::string to_string(Visibility v); +std::string to_string(Orientation o); +std::string to_string(Extent e); +std::string to_string(Interpolation interp); +std::string to_string(Axis axis); +std::string to_string(ListEditQual qual); +std::string to_string(Specifier specifier); +std::string to_string(Purpose purpose); +std::string to_string(Permission permission); +std::string to_string(Variability variability); +std::string to_string(SpecType spec_type); +std::string to_string(Kind kind); +std::string to_string(const Reference &reference); +std::string to_string(const Payload &payload); + +std::string to_string(const XformOp::OpType &ty); + +std::string to_string(GeomMesh::InterpolateBoundary interp_boundary); +std::string to_string(GeomMesh::SubdivisionScheme subd_scheme); +std::string to_string(GeomMesh::FaceVaryingLinearInterpolation fv); + +std::string to_string(const Path &path, bool show_full_path = true); +std::string to_string(const std::vector &v, bool show_full_path = true); + +// For debugging +std::string dump_path(const Path &p); + +template +std::string to_string(const std::vector &v, const uint32_t level = 0) { + std::stringstream ss; + ss << pprint::Indent(level) << "["; + + // TODO(syoyo): indent for large array + for (size_t i = 0; i < v.size(); i++) { + ss << to_string(v[i]); + if (i != (v.size() - 1)) { + ss << ", "; + } + } + ss << "]"; + return ss.str(); +} + +#if 0 +template<> +std::string to_string(const std::vector &v, const uint32_t level) { + std::stringstream ss; + ss << pprint::Indent(level) << "["; + + // TODO(syoyo): indent for large array + for (size_t i = 0; i < v.size(); i++) { + ss << quote(v[i]); + if (i != (v.size() -1)) { + ss << ", "; + } + } + ss << "]"; + return ss.str(); +} +#endif + +template +std::string to_string(const ListOp &op, const uint32_t indent_level = 0) { + std::stringstream ss; + ss << pprint::Indent(indent_level) << "ListOp(isExplicit " << op.IsExplicit() + << ") {\n"; + ss << pprint::Indent(indent_level) + << " explicit_items = " << to_string(op.GetExplicitItems()) << "\n"; + ss << pprint::Indent(indent_level) + << " added_items = " << to_string(op.GetAddedItems()) << "\n"; + ss << pprint::Indent(indent_level) + << " prepended_items = " << to_string(op.GetPrependedItems()) << "\n"; + ss << pprint::Indent(indent_level) + << " deleted_items = " << to_string(op.GetDeletedItems()) << "\n"; + ss << pprint::Indent(indent_level) + << " ordered_items = " << to_string(op.GetOrderedItems()) << "\n"; + ss << pprint::Indent(indent_level) << "}"; + + return ss.str(); +} + +// +// Setting `closing_brace` false won't emit `}`(for printing USD scene graph +// recursively). +// + +std::string to_string(const Model &model, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const Scope &scope, const uint32_t indent = 0, + bool closing_brace = true); +// std::string to_string(const Klass &klass, const uint32_t indent = 0, bool +// closing_brace = true); +std::string to_string(const GPrim &gprim, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const Xform &xform, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const GeomSphere &sphere, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const GeomMesh &mesh, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const GeomPoints &pts, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const GeomBasisCurves &curves, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const GeomNurbsCurves &curves, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const GeomCapsule &geom, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const GeomCone &geom, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const GeomCylinder &geom, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const GeomCube &geom, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const GeomCamera &camera, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const GeomSubset &subset, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const GeomSubset::ElementType ty); +std::string to_string(const GeomSubset::FamilyType ty); + +std::string to_string(const GeomBasisCurves::Wrap &v); +std::string to_string(const GeomBasisCurves::Type &v); +std::string to_string(const GeomBasisCurves::Basis &v); + +std::string to_string(const PointInstancer &instancer, const uint32_t indent = 0, + bool closing_brace = true); + + +std::string to_string(const SkelRoot &root, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const Skeleton &skel, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const SkelAnimation &anim, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const BlendShape &bs, const uint32_t indent = 0, + bool closing_brace = true); + +std::string to_string(const SphereLight &light, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const DomeLight &light, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const DiskLight &light, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const DistantLight &light, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const CylinderLight &light, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const RectLight &light, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const GeometryLight &light, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const PortalLight &light, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const PluginLight &light, const uint32_t indent = 0, + bool closing_brace = true); + +std::string to_string(const DomeLight::TextureFormat &texformat); + +std::string to_string(const Material &material, const uint32_t indent = 0, + bool closing_brace = true); + +// It will delegate to to_string() of concrete Shader type(e.g. +// UsdPreviewSurface) +std::string to_string(const Shader &shader, const uint32_t indent = 0, + bool closing_brace = true); + +std::string to_string(const UsdPreviewSurface &shader, + const uint32_t indent = 0, bool closing_brace = true); +std::string to_string(const UsdUVTexture &shader, const uint32_t indent = 0, + bool closing_brace = true); +std::string to_string(const UsdPrimvarReader_float &shader, + const uint32_t indent = 0, bool closing_brace = true); +std::string to_string(const UsdPrimvarReader_float2 &shader, + const uint32_t indent = 0, bool closing_brace = true); +std::string to_string(const UsdPrimvarReader_float3 &shader, + const uint32_t indent = 0, bool closing_brace = true); +std::string to_string(const UsdPrimvarReader_float4 &shader, + const uint32_t indent = 0, bool closing_brace = true); +std::string to_string(const UsdPrimvarReader_int &shader, + const uint32_t indent = 0, bool closing_brace = true); + +std::string to_string(const UsdUVTexture::SourceColorSpace v); +std::string to_string(const UsdUVTexture::Wrap v); + +std::string to_string(const GeomCamera::Projection &proj); +std::string to_string(const GeomCamera::StereoRole &role); + +std::string to_string(const tinyusdz::Animatable &v, + const uint32_t indent = 0, bool closing_brace = true); + +std::string to_string(const APISchemas::APIName &name); +std::string to_string(const CustomDataType &customData); + +std::string to_string(const Layer &layer, const uint32_t indent = 0, bool closing_brace = true); +std::string to_string(const PrimSpec &primspec, const uint32_t indent = 0, bool closing_brace = true); + +std::string to_string(const CollectionInstance::ExpansionRule rule); + +std::string print_xformOpOrder(const std::vector &xformOps, + const uint32_t indent); +std::string print_xformOps(const std::vector &xformOps, + const uint32_t indent); +std::string print_attr_metas(const AttrMeta &meta, const uint32_t indent); + +// varname = optional variable name which is used when meta.get_name() is empty. +std::string print_meta(const MetaVariable &meta, const uint32_t indent, bool emit_type_name, + const std::string &varname = std::string()); +std::string print_prim_metas(const PrimMeta &meta, const uint32_t indent); +std::string print_customData(const CustomDataType &customData, + const std::string &name, const uint32_t indent); +std::string print_variantSelectionMap(const VariantSelectionMap &m, + const uint32_t indent); +std::string print_variantSetStmt( + const std::map &vslist, const uint32_t indent); +std::string print_variantSetSpecStmt( + const std::map &vslist, const uint32_t indent); +std::string print_payload(const prim::PayloadList &payload, + const uint32_t indent); +std::string print_timesamples(const value::TimeSamples &v, + const uint32_t indent); +std::string print_rel_prop(const Property &prop, const std::string &name, + uint32_t indent); + +std::string print_prop(const Property &prop, const std::string &prop_name, + uint32_t indent); + +// Print properties. +// TODO: Deprecate this function. +std::string print_props(const std::map &props, + uint32_t indent); + +// tok_table: Manages property is already printed(built-in props) or not. +// propNames: Specify the order of property to print +// When `propNames` is empty, print all of items in `props`. +std::string print_props(const std::map &props, + /* input */ std::set &tok_table, + const std::vector &propNames, + uint32_t indent); + +std::string print_layer_metas(const LayerMetas &metas, const uint32_t indent); +std::string print_layer(const Layer &layer, const uint32_t indent); + +std::string print_material_binding(const MaterialBinding *mb, const uint32_t indent); +std::string print_collection(const Collection *coll, const uint32_t indent); + +} // namespace tinyusdz + +namespace std { + +std::ostream &operator<<(std::ostream &ofs, tinyusdz::Visibility v); +std::ostream &operator<<(std::ostream &ofs, tinyusdz::Extent v); +std::ostream &operator<<(std::ostream &ofs, const tinyusdz::Layer &layer); + +} // namespace std diff --git a/contrib/tinyusdz/tinyusdz_repo/src/prim-composition.cc b/contrib/tinyusdz/tinyusdz_repo/src/prim-composition.cc new file mode 100644 index 000000000..8a38f7057 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/prim-composition.cc @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022-Present Light Transport Entertainment Inc. +#include "composition.hh" + +namespace tinyusdz { +namespace prim { + +namespace { + +// Graphviz .dot wtier +class DotWriter +{ + public: + DotWriter() = default; + +}; + + + +} // namespace local + +} // namespace prim +} // namespace tinyusdz + + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/prim-pprint.hh b/contrib/tinyusdz/tinyusdz_repo/src/prim-pprint.hh new file mode 100644 index 000000000..0a7d9cab8 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/prim-pprint.hh @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. + +#pragma once + +#include +#include + +#include "prim-types.hh" + +namespace tinyusdz { +namespace prim { + +// +// Impelemnted in pprinter.cc at the moment. +// +std::string print_references(const ReferenceList &references, const uint32_t indent); +std::string print_payload(const PayloadList &payload, const uint32_t indent); +std::string print_layeroffset(const LayerOffset &layeroffset, const uint32_t indent); + +std::string print_prim(const Prim &prim, const uint32_t indent=0); +std::string print_primspec(const PrimSpec &primspec, const uint32_t indent=0); + +} // namespace prim + +inline std::string to_string(const Prim &prim) { + return prim::print_prim(prim); +} + +inline std::string to_string(const PrimSpec &primspec) { + return prim::print_primspec(primspec); +} + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/prim-reconstruct.cc b/contrib/tinyusdz/tinyusdz_repo/src/prim-reconstruct.cc new file mode 100644 index 000000000..3e472cfa4 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/prim-reconstruct.cc @@ -0,0 +1,4781 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2021 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +// Reconstruct concrete Prim from PropertyMap or PrimSpec. +// +// TODO: +// - [ ] Refactor code +// +#include "prim-reconstruct.hh" + +#include "prim-types.hh" +#include "str-util.hh" +#include "io-util.hh" +#include "tiny-format.hh" + +#include "usdGeom.hh" +#include "usdSkel.hh" +#include "usdLux.hh" +#include "usdShade.hh" + +#include "common-macros.inc" +#include "value-types.hh" + +// For PUSH_ERROR_AND_RETURN +#define PushError(s) if (err) { (*err) = s + (*err); } +#define PushWarn(s) if (warn) { (*warn) = s + (*err); } + +// __VA_ARGS__ does not allow empty, thus # of args must be 2+ +#define PUSH_WARN_F(s, ...) PUSH_WARN(fmt::format(s, __VA_ARGS__)) +#define PUSH_ERROR_AND_RETURN_F(s, ...) PUSH_ERROR_AND_RETURN(fmt::format(s, __VA_ARGS__)) + +// +// NOTE: +// +// There are mainly 5 variant of Primtive property(relationship/attribute) +// +// - TypedAttribute : Uniform only. `uniform T` or `uniform T var.connect` +// - TypedAttribute> : Varying. `T var`, `T var = val`, `T var.connect` or `T value.timeSamples` +// - optional : For output attribute(Just author it. e.g. `float outputs:rgb`) +// - Relationship : Typeless relation(e.g. `rel material:binding`) +// - TypedConnection : Typed relation(e.g. `token outputs:result = `) + +namespace tinyusdz { +namespace prim { + +//constexpr auto kTag = "[PrimReconstruct]"; + +constexpr auto kProxyPrim = "proxyPrim"; +constexpr auto kVisibility = "visibility"; +constexpr auto kExtent = "extent"; +constexpr auto kPurpose = "purpose"; +constexpr auto kMaterialBinding = "material:binding"; +constexpr auto kMaterialBindingCollection = "material:binding:collection"; +constexpr auto kMaterialBindingPreview = "material:binding:preview"; +constexpr auto kSkelSkeleton = "skel:skeleton"; +constexpr auto kSkelAnimationSource = "skel:animationSource"; +constexpr auto kSkelBlendShapes = "skel:blendShapes"; +constexpr auto kSkelBlendShapeTargets = "skel:blendShapeTargets"; +constexpr auto kInputsVarname = "inputs:varname"; + +/// +/// TinyUSDZ reconstruct some frequently used shaders(e.g. UsdPreviewSurface) +/// here, not in Tydra +/// +template +bool ReconstructShader( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + T *out, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options); + +namespace { + + +struct ParseResult +{ + enum class ResultCode + { + Success, + Unmatched, + AlreadyProcessed, + TypeMismatch, + VariabilityMismatch, + ConnectionNotAllowed, + InvalidConnection, + InternalError, + }; + + ResultCode code; + std::string err; +}; + +inline std::string to_string(ParseResult::ResultCode rescode) { + switch (rescode) { + case ParseResult::ResultCode::Success: return "success"; + case ParseResult::ResultCode::Unmatched: return "unmatched"; + case ParseResult::ResultCode::AlreadyProcessed: return "alreadyProcessed"; + case ParseResult::ResultCode::TypeMismatch: return "typeMismatch"; + case ParseResult::ResultCode::VariabilityMismatch: return "variabilityMismatch"; + case ParseResult::ResultCode::ConnectionNotAllowed: return "connectionNotAllowed"; + case ParseResult::ResultCode::InvalidConnection: return "invalidConnection"; + case ParseResult::ResultCode::InternalError: return "internalError"; + } + return "[[???ResultCode]]"; +} + +template +static nonstd::optional> ConvertToAnimatable(const primvar::PrimVar &var) +{ + Animatable dst; + + if (!var.is_valid()) { + DCOUT("is_valid failed"); + return nonstd::nullopt; + } + + if (var.is_scalar()) { + + if (auto pv = var.get_value()) { + dst.set(pv.value()); + + return std::move(dst); + } + } else if (var.is_timesamples()) { + for (size_t i = 0; i < var.ts_raw().size(); i++) { + const value::TimeSamples::Sample &s = var.ts_raw().get_samples()[i]; + + // Attribute Block? + if (s.blocked) { + dst.add_blocked_sample(s.t); + } else if (auto pv = s.value.get_value()) { + dst.add_sample(s.t, pv.value()); + } else { + // Type mismatch + DCOUT(i << "/" << var.ts_raw().size() << " type mismatch."); + return nonstd::nullopt; + } + } + + return std::move(dst); + } + + DCOUT("???"); + return nonstd::nullopt; +} + +// Require special treatment for Extent(float3[2]) +template<> +nonstd::optional> ConvertToAnimatable(const primvar::PrimVar &var) +{ + Animatable dst; + + if (!var.is_valid()) { + DCOUT("is_valid failed"); + return nonstd::nullopt; + } + + if (var.is_scalar()) { + + if (auto pv = var.get_value>()) { + if (pv.value().size() == 2) { + Extent ext; + ext.lower = pv.value()[0]; + ext.upper = pv.value()[1]; + + dst.set(ext); + + } else { + return nonstd::nullopt; + } + + return std::move(dst); + } + } else if (var.is_timesamples()) { + for (size_t i = 0; i < var.ts_raw().size(); i++) { + const value::TimeSamples::Sample &s = var.ts_raw().get_samples()[i]; + + // Attribute Block? + if (s.blocked) { + dst.add_blocked_sample(s.t); + } else if (auto pv = s.value.get_value>()) { + if (pv.value().size() == 2) { + Extent ext; + ext.lower = pv.value()[0]; + ext.upper = pv.value()[1]; + dst.add_sample(s.t, ext); + } else { + DCOUT(i << "/" << var.ts_raw().size() << " array size mismatch."); + return nonstd::nullopt; + } + } else { + // Type mismatch + DCOUT(i << "/" << var.ts_raw().size() << " type mismatch."); + return nonstd::nullopt; + } + } + + return std::move(dst); + } + + DCOUT("???"); + return nonstd::nullopt; +} + +#if 0 // TODO: remove. moved to prim-types.cc +static bool ConvertTokenAttributeToStringAttribute( + const TypedAttribute> &inp, + TypedAttribute> &out) { + + out.metas() = inp.metas(); + + if (inp.is_blocked()) { + out.set_blocked(true); + } else if (inp.is_value_empty()) { + out.set_value_empty(); + } else if (inp.is_connection()) { + out.set_connections(inp.get_connections()); + } else { + Animatable toks; + Animatable strs; + if (inp.get_value(&toks)) { + if (toks.is_scalar()) { + value::token tok; + toks.get_scalar(&tok); + strs.set(tok.str()); + } else if (toks.is_timesamples()) { + auto tok_ts = toks.get_timesamples(); + + for (auto &item : tok_ts.get_samples()) { + strs.add_sample(item.t, item.value.str()); + } + } else if (toks.is_blocked()) { + // TODO + return false; + } + } + out.set_value(strs); + } + + return true; +} +#endif + +static bool ConvertStringDataAttributeToStringAttribute( + const TypedAttribute> &inp, + TypedAttribute> &out) { + + out.metas() = inp.metas(); + + if (inp.is_blocked()) { + out.set_blocked(true); + } else if (inp.is_value_empty()) { + out.set_value_empty(); + } else if (inp.is_connection()) { + out.set_connections(inp.get_connections()); + } else { + Animatable toks; + Animatable strs; + if (inp.get_value(&toks)) { + if (toks.is_scalar()) { + value::StringData tok; + toks.get_scalar(&tok); + strs.set(tok.value); + } else if (toks.is_timesamples()) { + auto tok_ts = toks.get_timesamples(); + + for (auto &item : tok_ts.get_samples()) { + strs.add_sample(item.t, item.value.value); + } + } else if (toks.is_blocked()) { + // TODO + return false; + } + } + out.set_value(strs); + } + + return true; +} + +// For animatable attribute(`varying`) +template +static ParseResult ParseTypedAttribute(std::set &table, /* inout */ + const std::string prop_name, + const Property &prop, + const std::string &name, + TypedAttributeWithFallback> &target) +{ + ParseResult ret; + + if (prop_name.compare(name + ".connect") == 0) { + std::string propname = removeSuffix(name, ".connect"); + if (table.count(propname)) { + DCOUT("Already processed: " << prop_name); + ret.code = ParseResult::ResultCode::AlreadyProcessed; + return ret; + } + if (prop.is_connection()) { + if (auto pv = prop.get_relationTarget()) { + target.set_connection(pv.value()); + //target.variability = prop.attrib.variability; + target.metas() = prop.get_attribute().metas(); + table.insert(propname); + ret.code = ParseResult::ResultCode::Success; + DCOUT("Added as property with connection: " << propname); + return ret; + } else { + ret.code = ParseResult::ResultCode::InvalidConnection; + ret.err = "Connection target not found."; + return ret; + } + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Internal error. Unsupported/Unimplemented property type."; + return ret; + } + } else if (prop_name.compare(name) == 0) { + if (table.count(name)) { + ret.code = ParseResult::ResultCode::AlreadyProcessed; + return ret; + } + + const Attribute &attr = prop.get_attribute(); + + if (prop.is_connection()) { + if (attr.is_connection()) { // Ensure Attribute is also return true for is_connection + target.set_connections(attr.connections()); + target.metas() = attr.metas(); + table.insert(prop_name); + ret.code = ParseResult::ResultCode::Success; + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Internal error. Invalid Property with Attribute connection."; + } + return ret; + } + + + std::string attr_type_name = attr.type_name(); + if ((value::TypeTraits::type_name() == attr_type_name) || (value::TypeTraits::underlying_type_name() == attr_type_name)) { + if (prop.get_property_type() == Property::Type::EmptyAttrib) { + DCOUT("Added prop with empty value: " << name); + target.set_value_empty(); + target.metas() = attr.metas(); + table.insert(name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else if (prop.get_property_type() == Property::Type::Attrib) { + + DCOUT("Adding typed prop: " << name); + + if (attr.is_blocked()) { + // e.g. "float radius = None" + target.set_blocked(true); + } else if (attr.variability() == Variability::Uniform) { + DCOUT("Property is uniform: " << name); + // e.g. "float radius = 1.2" + if (!attr.get_var().is_scalar()) { + ret.code = ParseResult::ResultCode::VariabilityMismatch; + ret.err = fmt::format("TimeSample value is assigned to `uniform` property `{}", name); + return ret; + } + + if (auto pv = attr.get_value()) { + target.set_value(pv.value()); + } else { + ret.code = ParseResult::ResultCode::TypeMismatch; + ret.err = fmt::format("Fallback. Failed to retrieve value with requested type `{}`.", value::TypeTraits::type_name()); + return ret; + } + + } else if (attr.get_var().is_timesamples()) { + DCOUT("Property is timesamples: " << name); + // e.g. "float radius.timeSamples = {0: 1.2, 1: 2.3}" + + Animatable anim; + if (auto av = ConvertToAnimatable(attr.get_var())) { + anim = av.value(); + target.set_value(anim); + } else { + // Conversion failed. + DCOUT("ConvertToAnimatable failed."); + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Converting Attribute data failed. Maybe TimeSamples have values with different types?"; + return ret; + } + } else if (attr.get_var().is_scalar()) { + DCOUT("Property is scalar: " << name); + if (auto pv = attr.get_value()) { + target.set_value(pv.value()); + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Invalid attribute value."; + return ret; + } + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Invalid attribute value."; + return ret; + } + + target.metas() = attr.metas(); + table.insert(name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else { + DCOUT("Invalid Property.type"); + ret.err = "Invalid Property type(internal error)"; + ret.code = ParseResult::ResultCode::InternalError; + return ret; + } + } else { + DCOUT("tyname = " << value::TypeTraits::type_name() << ", attr.type = " << attr_type_name); + ret.code = ParseResult::ResultCode::TypeMismatch; + std::stringstream ss; + ss << "Property type mismatch. " << name << " expects type `" + << value::TypeTraits::type_name() + << "` but defined as type `" << attr_type_name << "`"; + ret.err = ss.str(); + return ret; + } + } + + ret.code = ParseResult::ResultCode::Unmatched; + return ret; +} + +// For 'uniform' attribute +template +static ParseResult ParseTypedAttribute(std::set &table, /* inout */ + const std::string prop_name, + const Property &prop, + const std::string &name, + TypedAttributeWithFallback &target) /* out */ +{ + ParseResult ret; + + if (prop_name.compare(name + ".connect") == 0) { + std::string propname = removeSuffix(name, ".connect"); + if (table.count(propname)) { + DCOUT("Already processed: " << prop_name); + ret.code = ParseResult::ResultCode::AlreadyProcessed; + return ret; + } + if (prop.is_connection()) { + const Attribute &attr = prop.get_attribute(); + if (attr.is_connection()) { + target.set_connections(attr.connections()); + //target.variability = prop.attrib.variability; + target.metas() = prop.get_attribute().metas(); + table.insert(propname); + ret.code = ParseResult::ResultCode::Success; + DCOUT("Added as property with connection: " << propname); + return ret; + } else { + ret.code = ParseResult::ResultCode::InvalidConnection; + ret.err = "Connection target not found."; + return ret; + } + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Internal error. Unsupported/Unimplemented property type."; + return ret; + } + } else if (prop_name.compare(name) == 0) { + if (table.count(name)) { + ret.code = ParseResult::ResultCode::AlreadyProcessed; + return ret; + } + + if (prop.is_connection()) { + const Attribute &attr = prop.get_attribute(); + if (attr.is_connection()) { + target.set_connections(attr.connections()); + //target.variability = prop.attrib.variability; + target.metas() = prop.get_attribute().metas(); + table.insert(prop_name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Internal error. Invalid property with connection."; + return ret; + } + } + + const Attribute &attr = prop.get_attribute(); + + std::string attr_type_name = attr.type_name(); + if ((value::TypeTraits::type_name() == attr_type_name) || (value::TypeTraits::underlying_type_name() == attr_type_name)) { + if (prop.get_property_type() == Property::Type::EmptyAttrib) { + DCOUT("Added prop with empty value: " << name); + target.set_value_empty(); + target.metas() = attr.metas(); + table.insert(name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else if (prop.get_property_type() == Property::Type::Attrib) { + DCOUT("Adding prop: " << name); + + if (prop.get_attribute().variability() != Variability::Uniform) { + ret.code = ParseResult::ResultCode::VariabilityMismatch; + ret.err = fmt::format("Attribute `{}` must be `uniform` variability.", name); + return ret; + } + + if (attr.is_blocked()) { + target.set_blocked(true); + } else if (attr.get_var().is_scalar()) { + if (auto pv = attr.get_value()) { + target.set_value(pv.value()); + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Internal data corrupsed."; + return ret; + } + } else { + ret.code = ParseResult::ResultCode::VariabilityMismatch; + ret.err = "TimeSample or corrupted value assigned to a property where `uniform` variability is set."; + return ret; + } + + target.metas() = attr.metas(); + table.insert(name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else { + DCOUT("Invalid Property.type"); + ret.err = "Invalid Property type(internal error)"; + ret.code = ParseResult::ResultCode::InternalError; + return ret; + } + } else { + DCOUT("tyname = " << value::TypeTraits::type_name() << ", attr.type = " << attr_type_name); + ret.code = ParseResult::ResultCode::TypeMismatch; + std::stringstream ss; + ss << "Property type mismatch. " << name << " expects type `" + << value::TypeTraits::type_name() + << "` but defined as type `" << attr_type_name << "`"; + ret.err = ss.str(); + return ret; + } + } + + ret.code = ParseResult::ResultCode::Unmatched; + return ret; +} + +// For animatable attribute(`varying`) +template +static ParseResult ParseTypedAttribute(std::set &table, /* inout */ + const std::string prop_name, + const Property &prop, + const std::string &name, + TypedAttribute> &target) /* out */ +{ + ParseResult ret; + + // TODO: Remove ".connect" check, since prop_name now does not contain ".connect" + if (prop_name.compare(name + ".connect") == 0) { + std::string propname = removeSuffix(name, ".connect"); + if (table.count(propname)) { + DCOUT("Already processed: " << prop_name); + ret.code = ParseResult::ResultCode::AlreadyProcessed; + return ret; + } + if (prop.is_connection()) { + const Attribute &attr = prop.get_attribute(); + if (attr.is_connection()) { + target.set_connections(attr.connections()); + //target.variability = prop.attrib.variability; + target.metas() = prop.get_attribute().metas(); + table.insert(propname); + ret.code = ParseResult::ResultCode::Success; + DCOUT("Added as property with connection: " << propname); + return ret; + } else { + ret.code = ParseResult::ResultCode::InvalidConnection; + ret.err = "Connection target not found."; + return ret; + } + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Internal error. Unsupported/Unimplemented property type."; + return ret; + } + } else if (prop_name.compare(name) == 0) { + if (table.count(name)) { + ret.code = ParseResult::ResultCode::AlreadyProcessed; + return ret; + } + + if (prop.is_connection()) { + const Attribute &attr = prop.get_attribute(); + if (attr.is_connection()) { + target.set_connections(attr.connections()); + //target.variability = prop.attrib.variability; + target.metas() = prop.get_attribute().metas(); + table.insert(prop_name); + ret.code = ParseResult::ResultCode::Success; + DCOUT("Added connected attribute."); + return ret; + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Internal error. Invalid property with connection."; + return ret; + } + } + + const Attribute &attr = prop.get_attribute(); + + std::string attr_type_name = attr.type_name(); + if ((value::TypeTraits::type_name() == attr_type_name) || (value::TypeTraits::underlying_type_name() == attr_type_name)) { + if (prop.get_property_type() == Property::Type::EmptyAttrib) { + DCOUT("Added prop with empty value: " << name); + target.set_value_empty(); + target.metas() = attr.metas(); + table.insert(name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else if (prop.get_property_type() == Property::Type::Attrib) { + + DCOUT("Adding typed attribute: " << name); + + if (attr.is_blocked()) { + DCOUT("Attribute is blocked: " << name); + // e.g. "uniform float radius = None" + target.set_blocked(true); + } else if (attr.variability() == Variability::Uniform) { + DCOUT("Attribute is uniform: " << name); + // e.g. "uniform float radius = 1.2" + if (!attr.get_var().is_scalar()) { + ret.code = ParseResult::ResultCode::VariabilityMismatch; + ret.err = fmt::format("TimeSample value is assigned to `uniform` property `{}", name); + return ret; + } + + if (auto pv = attr.get_value()) { + target.set_value(pv.value()); + } else { + ret.code = ParseResult::ResultCode::TypeMismatch; + ret.err = fmt::format("Failed to retrieve value with requested type `{}`.", value::TypeTraits::type_name()); + return ret; + } + + } else if (attr.get_var().is_timesamples()) { + DCOUT("Attribute is timesamples: " << name); + // e.g. "float radius.timeSamples = {0: 1.2, 1: 2.3}" + + Animatable anim; + if (auto av = ConvertToAnimatable(attr.get_var())) { + anim = av.value(); + target.set_value(anim); + } else { + // Conversion failed. + DCOUT("ConvertToAnimatable failed."); + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Converting Attribute data failed. Maybe TimeSamples have values with different types?"; + return ret; + } + } else if (attr.get_var().is_scalar()) { + DCOUT("Attribute is scalar: " << name); + if (auto pv = attr.get_var().get_value()) { + target.set_value(pv.value()); + } else { + ret.code = ParseResult::ResultCode::TypeMismatch; + ret.err = fmt::format("Failed to retrieve value with requested type `{}`.", value::TypeTraits::type_name()); + return ret; + } + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Invalid or Unsupported attribute data."; + return ret; + } + + DCOUT("Added typed attribute: " << name); + + target.metas() = attr.metas(); + table.insert(name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else { + DCOUT("Invalid Property.type"); + ret.err = "Invalid Property type(internal error)"; + ret.code = ParseResult::ResultCode::InternalError; + return ret; + } + } else { + DCOUT("tyname = " << value::TypeTraits::type_name() << ", attr.type = " << attr_type_name); + ret.code = ParseResult::ResultCode::TypeMismatch; + std::stringstream ss; + ss << "Property type mismatch. " << name << " expects type `" + << value::TypeTraits::type_name() + << "` but defined as type `" << attr_type_name << "`"; + ret.err = ss.str(); + return ret; + } + } + + ret.code = ParseResult::ResultCode::Unmatched; + return ret; +} + +// TODO: Unify code with TypedAttribute> variant +template +static ParseResult ParseTypedAttribute(std::set &table, /* inout */ + const std::string prop_name, + const Property &prop, + const std::string &name, + TypedAttribute &target) /* out */ +{ + ParseResult ret; + + DCOUT(fmt::format("prop name {}", prop_name)); + + if (prop_name.compare(name + ".connect") == 0) { + std::string propname = removeSuffix(name, ".connect"); + if (table.count(propname)) { + DCOUT("Already processed: " << prop_name); + ret.code = ParseResult::ResultCode::AlreadyProcessed; + return ret; + } + if (prop.is_connection()) { + const Attribute &attr = prop.get_attribute(); + if (attr.is_connection()) { + target.set_connections(attr.connections()); + //target.variability = prop.attrib.variability; + target.metas() = prop.get_attribute().metas(); + table.insert(propname); + ret.code = ParseResult::ResultCode::Success; + DCOUT("Added as property with connection: " << propname); + return ret; + } else { + ret.code = ParseResult::ResultCode::InvalidConnection; + ret.err = "Connection target not found."; + return ret; + } + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Internal error. Unsupported/Unimplemented property type."; + return ret; + } + } else if (prop_name.compare(name) == 0) { + DCOUT(fmt::format("prop name match {}", name)); + if (table.count(name)) { + ret.code = ParseResult::ResultCode::AlreadyProcessed; + return ret; + } + + if (prop.is_connection()) { + const Attribute &attr = prop.get_attribute(); + if (attr.is_connection()) { + target.set_connections(attr.connections()); + //target.variability = prop.attrib.variability; + target.metas() = prop.get_attribute().metas(); + table.insert(prop_name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Internal error. Invalid property with connection."; + return ret; + } + } + + const Attribute &attr = prop.get_attribute(); + + std::string attr_type_name = attr.type_name(); + DCOUT(fmt::format("prop name {}, type = {}", prop_name, attr_type_name)); + if ((value::TypeTraits::type_name() == attr_type_name) || (value::TypeTraits::underlying_type_name() == attr_type_name)) { + if (prop.get_property_type() == Property::Type::EmptyAttrib) { + DCOUT("Added prop with empty value: " << name); + target.set_value_empty(); + target.metas() = attr.metas(); + table.insert(name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else if (prop.get_property_type() == Property::Type::Attrib) { + + DCOUT("Adding typed attribute: " << name); + + if (prop.get_attribute().variability() != Variability::Uniform) { + ret.code = ParseResult::ResultCode::VariabilityMismatch; + ret.err = fmt::format("Attribute `{}` must be `uniform` variability.", name); + return ret; + } + + if (attr.is_blocked()) { + target.set_blocked(true); + } else if (attr.get_var().is_scalar()) { + if (auto pv = attr.get_value()) { + target.set_value(pv.value()); + } else { + ret.code = ParseResult::ResultCode::VariabilityMismatch; + ret.err = "Internal data corrupsed."; + return ret; + } + } else { + ret.code = ParseResult::ResultCode::VariabilityMismatch; + ret.err = "TimeSample or corrupted value assigned to a property where `uniform` variability is set."; + return ret; + } + + target.metas() = attr.metas(); + table.insert(name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else { + DCOUT("Invalid Property.type"); + ret.err = "Invalid Property type(internal error)"; + ret.code = ParseResult::ResultCode::InternalError; + return ret; + } + } else { + DCOUT("tyname = " << value::TypeTraits::type_name() << ", attr.type = " << attr_type_name); + ret.code = ParseResult::ResultCode::TypeMismatch; + std::stringstream ss; + ss << "Property type mismatch. " << name << " expects type `" + << value::TypeTraits::type_name() + << "` but defined as type `" << attr_type_name << "`"; + ret.err = ss.str(); + return ret; + } + } + + ret.code = ParseResult::ResultCode::Unmatched; + return ret; +} + +// Special case for Extent(float3[2]) type. +// TODO: Reuse code of ParseTypedAttribute as much as possible +static ParseResult ParseExtentAttribute(std::set &table, /* inout */ + const std::string prop_name, + const Property &prop, + const std::string &name, + TypedAttribute> &target) /* out */ +{ + ParseResult ret; + + if (prop_name.compare(name + ".connect") == 0) { + std::string propname = removeSuffix(name, ".connect"); + if (table.count(propname)) { + DCOUT("Already processed: " << prop_name); + ret.code = ParseResult::ResultCode::AlreadyProcessed; + return ret; + } + if (prop.is_connection()) { + const Attribute &attr = prop.get_attribute(); + if (attr.is_connection()) { + target.set_connections(attr.connections()); + //target.variability = prop.attrib.variability; + target.metas() = prop.get_attribute().metas(); + table.insert(propname); + ret.code = ParseResult::ResultCode::Success; + DCOUT("Added as property with connection: " << propname); + return ret; + } else { + ret.code = ParseResult::ResultCode::InvalidConnection; + ret.err = "Connection target not found."; + return ret; + } + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Internal error. Unsupported/Unimplemented property type."; + return ret; + } + } else if (prop_name.compare(name) == 0) { + if (table.count(name)) { + ret.code = ParseResult::ResultCode::AlreadyProcessed; + return ret; + } + + if (prop.is_connection()) { + const Attribute &attr = prop.get_attribute(); + if (attr.is_connection()) { + target.set_connections(attr.connections()); + //target.variability = prop.attrib.variability; + target.metas() = prop.get_attribute().metas(); + table.insert(prop_name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Internal error. Invalid property with connection."; + return ret; + } + } + + const Attribute &attr = prop.get_attribute(); + + std::string attr_type_name = attr.type_name(); + if (prop.get_property_type() == Property::Type::EmptyAttrib) { + DCOUT("Added prop with empty value: " << name); + target.set_value_empty(); + target.metas() = attr.metas(); + table.insert(name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else if (prop.get_property_type() == Property::Type::Attrib) { + + DCOUT("Adding typed attribute: " << name); + + if (attr.is_blocked()) { + // e.g. "float3[] extent = None" + target.set_blocked(true); + } else if (attr.get_var().is_scalar()){ + // + // No variability check. allow `uniform extent`(promote to varying) + // + if (auto pv = attr.get_value>()) { + if (pv.value().size() != 2) { + ret.code = ParseResult::ResultCode::TypeMismatch; + ret.err = fmt::format("`extent` must be `float3[2]`, but got array size {}", pv.value().size()); + return ret; + } + + Extent ext; + ext.lower = pv.value()[0]; + ext.upper = pv.value()[1]; + + target.set_value(ext); + + } else { + ret.code = ParseResult::ResultCode::TypeMismatch; + ret.err = fmt::format("`extent` must be type `float3[]`, but got type `{}", attr.type_name()); + return ret; + } + + } else if (attr.get_var().is_timesamples()) { + // e.g. "float3[] extent.timeSamples = ..." + + Animatable anim; + if (auto av = ConvertToAnimatable(attr.get_var())) { + anim = av.value(); + target.set_value(anim); + } else { + // Conversion failed. + DCOUT("ConvertToAnimatable failed."); + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Converting Attribute data failed. Maybe TimeSamples have values with different types or invalid array size?"; + return ret; + } + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Invalid or Unsupported Extent attribute value."; + return ret; + } + + DCOUT("Added Extent attribute: " << name); + + target.metas() = attr.metas(); + table.insert(name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else { + DCOUT("Invalid Property.type"); + ret.err = "Invalid Property type(internal error)"; + ret.code = ParseResult::ResultCode::InternalError; + return ret; + } + } + + ret.code = ParseResult::ResultCode::Unmatched; + return ret; +} + + +#if 0 +template +static ParseResult ParseTypedProperty(std::set &table, /* inout */ + const std::string prop_name, + const Property &prop, + const std::string &name, + TypedProperty &target) /* out */ +{ + ParseResult ret; + + DCOUT("Parsing typed property: " << prop_name); + + if (prop_name.compare(name + ".connect") == 0) { + std::string propname = removeSuffix(name, ".connect"); + if (table.count(propname)) { + DCOUT("Already processed: " << prop_name); + ret.code = ParseResult::ResultCode::AlreadyProcessed; + return ret; + } + if (prop.IsConnection()) { + if (auto pv = prop.get_relationTarget()) { + target.target = pv.value(); + target.variability = prop.attrib.variability; + target.meta = prop.attrib.meta; + table.insert(propname); + ret.code = ParseResult::ResultCode::Success; + DCOUT("Added as property with connection: " << propname); + return ret; + } else { + ret.code = ParseResult::ResultCode::InvalidConnection; + ret.err = "Connection target not found."; + return ret; + } + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Internal error. Unsupported/Unimplemented property type."; + return ret; + } + } else if (prop_name.compare(name) == 0) { + if (table.count(name)) { + ret.code = ParseResult::ResultCode::AlreadyProcessed; + return ret; + } + const Attribute &attr = prop.attrib; + + DCOUT("prop is_rel = " << prop.is_relationship() << ", is_conn = " << prop.IsConnection()); + + if (prop.IsConnection()) { + if (auto pv = prop.get_relationTarget()) { + target.target = pv.value(); + target.variability = prop.attrib.variability; + target.meta = prop.attrib.meta; + table.insert(prop_name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Internal error. Invalid property with connection."; + return ret; + } + + } else if (prop.IsAttrib()) { + + DCOUT("attrib.type = " << value::TypeTraits::type_name() << ", attr.var.type= " << attr.type_name()); + + std::string attr_type_name = attr.type_name(); + + if ((value::TypeTraits::type_name() == attr_type_name) || (value::TypeTraits::underlying_type_name() == attr_type_name)) { + if (prop.type == Property::Type::EmptyAttrib) { + target.define_only = true; + target.variability = attr.variability; + target.meta = attr.meta; + table.insert(name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else if (prop.type == Property::Type::Attrib) { + DCOUT("Adding prop: " << name); + + Animatable anim; + + if (attr.blocked()) { + anim.blocked = true; + } else { + if (auto av = ConvertToAnimatable(attr.get_var())) { + anim = av.value(); + } else { + // Conversion failed. + DCOUT("ConvertToAnimatable failed."); + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Converting Attribute data failed. Maybe TimeSamples have values with different types?"; + return ret; + } + } + + target.value = anim; + target.variability = attr.variability; + target.meta = attr.meta; + table.insert(name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else { + DCOUT("Invalid Property.type"); + ret.err = "Invalid Property type(internal error)"; + ret.code = ParseResult::ResultCode::InternalError; + return ret; + } + } else { + DCOUT("tyname = " << value::TypeTraits::type_name() << ", attr.type = " << attr_type_name); + ret.code = ParseResult::ResultCode::TypeMismatch; + std::stringstream ss; + ss << "Property type mismatch. " << name << " expects type `" + << value::TypeTraits::type_name() + << "` but defined as type `" << attr_type_name << "`"; + ret.err = ss.str(); + return ret; + } + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Internal error. Unsupported/Unimplemented property type."; + return ret; + } + } + + ret.code = ParseResult::ResultCode::Unmatched; + return ret; +} +#endif + + +// Empty allowedTokens = allow all +template +static nonstd::expected CheckAllowedTokens( + const std::array, N> &allowedTokens, + const std::string &tok) { + if (allowedTokens.empty()) { + return true; + } + + for (size_t i = 0; i < N; i++) { + if (tok.compare(std::get<1>(allowedTokens[i])) == 0) { + return true; + } + } + + std::vector toks; + for (size_t i = 0; i < N; i++) { + toks.push_back(std::get<1>(allowedTokens[i])); + } + + std::string s = join(", ", tinyusdz::quote(toks)); + + return nonstd::make_unexpected("Allowed tokens are [" + s + "] but got " + + quote(tok) + "."); +}; + +// Allowed syntax: +// "T varname" +template +static ParseResult ParseShaderOutputTerminalAttribute(std::set &table, /* inout */ + const std::string prop_name, + const Property &prop, + const std::string &name, + TypedTerminalAttribute &target) /* out */ +{ + ParseResult ret; + +#if 0 // Old code: TODO: Remove + if (prop_name.compare(name + ".connect") == 0) { + ret.code = ParseResult::ResultCode::ConnectionNotAllowed; + ret.err = "Connection is not allowed for output terminal attribute."; + return ret; +#endif + if (prop_name.compare(name) == 0) { + if (table.count(name)) { + ret.code = ParseResult::ResultCode::AlreadyProcessed; + return ret; + } + + if (prop.is_connection()) { + ret.code = ParseResult::ResultCode::ConnectionNotAllowed; + ret.err = "Connection is not allowed for output terminal attribute."; + return ret; + } else { + + if (prop.get_property_type() != Property::Type::EmptyAttrib) { + DCOUT("Output Invalid shader output terminal attribute"); + ret.err = "No value should be assigned for shader output terminal attribute."; + ret.code = ParseResult::ResultCode::InvalidConnection; + return ret; + } + + const Attribute &attr = prop.get_attribute(); + + std::string attr_type_name = attr.type_name(); + + bool attr_is_role_type = value::IsRoleType(attr_type_name); + + DCOUT("attrname = " << name); + DCOUT("value typename = " << value::TypeTraits::type_name()); + DCOUT("attr-type_name = " << attr_type_name); + + + // First check if both types are same, then + // Allow either type is role-types(e.g. allow color3f attribute for TypedTerminalAttribute) + // TODO: Allow both role-types case?(e.g. point3f attribute for TypedTerminalAttribute) + if (value::TypeTraits::type_name() == attr_type_name) { + DCOUT("Author output terminal attribute: " << name); + target.set_authored(true); + target.metas() = prop.get_attribute().metas(); + table.insert(name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else if (value::TypeTraits::is_role_type()) { + if (attr_is_role_type) { + ret.code = ParseResult::ResultCode::TypeMismatch; + ret.err = fmt::format("Attribute type mismatch. {} expects type `{}` but defined as type `{}`.", name, value::TypeTraits::type_name(), attr_type_name); + return ret; + } else { + if (value::TypeTraits::underlying_type_name() == attr_type_name) { + target.set_authored(true); + target.set_actual_type_name(attr_type_name); + target.metas() = prop.get_attribute().metas(); + table.insert(name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else { + ret.code = ParseResult::ResultCode::TypeMismatch; + ret.err = fmt::format("Attribute type mismatch. {} expects type `{}`(and its underlying types) but defined as type `{}`.", name, value::TypeTraits::type_name(), attr_type_name); + return ret; + } + } + } else if (attr_is_role_type) { + if (value::TypeTraits::is_role_type()) { + ret.code = ParseResult::ResultCode::TypeMismatch; + ret.err = fmt::format("Attribute type mismatch. {} expects type `{}` but defined as type `{}`.", name, value::TypeTraits::type_name(), attr_type_name); + return ret; + } else { + uint32_t attr_underlying_type_id = value::GetUnderlyingTypeId(attr_type_name); + if (value::TypeTraits::type_id() == attr_underlying_type_id) { + target.set_authored(true); + target.set_actual_type_name(attr_type_name); + target.metas() = prop.get_attribute().metas(); + table.insert(name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else { + ret.code = ParseResult::ResultCode::TypeMismatch; + ret.err = fmt::format("Attribute type mismatch. {} expects type `{}` but defined as type `{}`(and its underlying types).", name, value::TypeTraits::type_name(), attr_type_name); + return ret; + } + } + + } else { + DCOUT("attr.type = " << attr_type_name); + ret.code = ParseResult::ResultCode::TypeMismatch; + ret.err = fmt::format("Property type mismatch. {} expects type `{}` but defined as type `{}`.", name, value::TypeTraits::type_name(), attr_type_name); + return ret; + } + } + } + + ret.code = ParseResult::ResultCode::Unmatched; + return ret; +} + +#if 0 // TODO: Remove since not used. +// Allowed syntax: +// "token outputs:surface" +// "token outputs:surface.connect = " +static ParseResult ParseShaderOutputProperty(std::set &table, /* inout */ + const std::string prop_name, + const Property &prop, + const std::string &name, + nonstd::optional &target) /* out */ +{ + ParseResult ret; + + if (prop_name.compare(name + ".connect") == 0) { + std::string propname = removeSuffix(name, ".connect"); + if (table.count(propname)) { + ret.code = ParseResult::ResultCode::AlreadyProcessed; + return ret; + } + if (auto pv = prop.get_relationTarget()) { + Relationship rel; + rel.set(pv.value()); + rel.metas() = prop.get_attribute().metas(); + target = rel; + table.insert(propname); + ret.code = ParseResult::ResultCode::Success; + return ret; + } + } else if (prop_name.compare(name) == 0) { + if (table.count(name)) { + ret.code = ParseResult::ResultCode::AlreadyProcessed; + return ret; + } + + if (prop.is_connection()) { + const Attribute &attr = prop.get_attribute(); + if (attr.is_connection()) { + Relationship rel; + std::vector conns = attr.connections(); + + if (conns.size() == 0) { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Invalid shader output attribute with connection. connection targetPath size is zero."; + return ret; + } + + if (conns.size() == 1) { + rel.set(conns[0]); + } else if (conns.size() > 1) { + rel.set(conns); + } + + rel.metas() = prop.get_attribute().metas(); + target = rel; + table.insert(prop_name); + ret.code = ParseResult::ResultCode::Success; + return ret; + + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Invalid shader output attribute with connection."; + return ret; + } + } else { + + const Attribute &attr = prop.get_attribute(); + + std::string attr_type_name = attr.type_name(); + if (value::TypeTraits::type_name() == attr_type_name) { + if (prop.get_property_type() == Property::Type::EmptyAttrib) { + Relationship rel; + rel.set_novalue(); + rel.metas() = prop.get_attribute().metas(); + table.insert(name); + target = rel; + ret.code = ParseResult::ResultCode::Success; + return ret; + } else { + DCOUT("Output Invalid Property.type"); + ret.err = "Invalid connection or value assigned for output attribute."; + ret.code = ParseResult::ResultCode::InvalidConnection; + return ret; + } + } else { + DCOUT("attr.type = " << attr.type_name()); + ret.code = ParseResult::ResultCode::TypeMismatch; + std::stringstream ss; + ss << "Property type mismatch. " << name << " expects type `token` but defined as type `" << attr.type_name() << "`"; + ret.err = ss.str(); + return ret; + } + } + } + + ret.code = ParseResult::ResultCode::Unmatched; + return ret; +} +#endif + +// Allowed syntax: +// "token outputs:surface.connect = " +static ParseResult ParseShaderInputConnectionProperty(std::set &table, /* inout */ + const std::string prop_name, + const Property &prop, + const std::string &name, + TypedConnection &target) /* out */ +{ + ParseResult ret; + ret.code = ParseResult::ResultCode::InternalError; + +#if 0 + if (prop_name.compare(name + ".connect") == 0) { + std::string propname = removeSuffix(name, ".connect"); + if (table.count(propname)) { + ret.code = ParseResult::ResultCode::AlreadyProcessed; + return ret; + } + if (auto pv = prop.get_relationTarget()) { + TypedConnection conn; + conn.set(pv.value()); + conn.metas() = prop.get_attribute().metas(); + target = conn; + table.insert(propname); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Property does not contain connectionPath."; + return ret; + } +#endif + if (prop_name.compare(name) == 0) { + if (table.count(name)) { + ret.code = ParseResult::ResultCode::AlreadyProcessed; + return ret; + } + + // allow empty value + if (prop.is_empty()) { + target.set_empty(); + target.metas() = prop.get_attribute().metas(); + table.insert(prop_name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else if (prop.is_connection()) { + const Attribute &attr = prop.get_attribute(); + if (attr.is_connection()) { + target.set(attr.connections()); + target.metas() = prop.get_attribute().metas(); + + table.insert(prop_name); + ret.code = ParseResult::ResultCode::Success; + return ret; + } else { + ret.code = ParseResult::ResultCode::InternalError; + ret.err = "Property is invalid Attribute connection."; + return ret; + } + } else { + std::stringstream ss; + ss << "Property must be Attribute connection."; + ret.code = ParseResult::ResultCode::InternalError; + ret.err = ss.str(); + return ret; + } + } + + ret.code = ParseResult::ResultCode::Unmatched; + return ret; +} + +// Rel with single targetPath(or empty) +#define PARSE_SINGLE_TARGET_PATH_RELATION(__table, __prop, __propname, __target) \ + if (prop.first == __propname) { \ + if (__table.count(__propname)) { \ + continue; \ + } \ + if (!prop.second.is_relationship()) { \ + PUSH_ERROR_AND_RETURN(fmt::format("Property `{}` must be a Relationship.", __propname)); \ + } \ + const Relationship &rel = prop.second.get_relationship(); \ + if (rel.is_path()) { \ + __target = rel; \ + table.insert(prop.first); \ + DCOUT("Added rel " << __propname); \ + continue; \ + } else if (rel.is_pathvector()) { \ + if (rel.targetPathVector.size() == 1) { \ + __target = rel; \ + table.insert(prop.first); \ + DCOUT("Added rel " << __propname); \ + continue; \ + } \ + PUSH_ERROR_AND_RETURN(fmt::format("`{}` target is empty or has mutiple Paths. Must be single Path.", __propname)); \ + } else if (!rel.has_value()) { \ + /* define-only. accept */ \ + __target = rel; \ + table.insert(prop.first); \ + DCOUT("Added rel " << __propname); \ + } else { \ + PUSH_ERROR_AND_RETURN(fmt::format("`{}` target must be Path.", __propname)); \ + } \ + } + +// Rel with targetPaths(single path or array of Paths) +#define PARSE_TARGET_PATHS_RELATION(__table, __prop, __propname, __target) \ + if (prop.first == __propname) { \ + if (__table.count(__propname)) { \ + continue; \ + } \ + if (!prop.second.is_relationship()) { \ + PUSH_ERROR_AND_RETURN(fmt::format("`{}` must be a Relationship", __propname)); \ + } \ + const Relationship &rel = prop.second.get_relationship(); \ + __target = rel; \ + table.insert(prop.first); \ + DCOUT("Added rel " << __propname); \ + continue; \ + } + + +#define PARSE_SHADER_TERMINAL_ATTRIBUTE(__table, __prop, __name, __klass, __target) { \ + ParseResult ret = ParseShaderOutputTerminalAttribute(__table, __prop.first, __prop.second, __name, __target); \ + if (ret.code == ParseResult::ResultCode::Success || ret.code == ParseResult::ResultCode::AlreadyProcessed) { \ + DCOUT("Added shader terminal attribute: " << __name); \ + continue; /* got it */\ + } else if (ret.code == ParseResult::ResultCode::Unmatched) { \ + /* go next */ \ + } else { \ + PUSH_ERROR_AND_RETURN(fmt::format("Parsing shader output property `{}` failed. Error: {}", __name, ret.err)); \ + } \ +} + +#if 0 // TODO: Remove since not used. +#define PARSE_SHADER_OUTPUT_PROPERTY(__table, __prop, __name, __klass, __target) { \ + ParseResult ret = ParseShaderOutputProperty(__table, __prop.first, __prop.second, __name, __target); \ + if (ret.code == ParseResult::ResultCode::Success || ret.code == ParseResult::ResultCode::AlreadyProcessed) { \ + DCOUT("Added shader output property: " << __name); \ + continue; /* got it */\ + } else if (ret.code == ParseResult::ResultCode::Unmatched) { \ + /* go next */ \ + } else { \ + PUSH_ERROR_AND_RETURN(fmt::format("Parsing shader output property `{}` failed. Error: {}", __name, ret.err)); \ + } \ +} +#endif + +#define PARSE_SHADER_INPUT_CONNECTION_PROPERTY(__table, __prop, __name, __klass, __target) { \ + ParseResult ret = ParseShaderInputConnectionProperty(__table, __prop.first, __prop.second, __name, __target); \ + if (ret.code == ParseResult::ResultCode::Success || ret.code == ParseResult::ResultCode::AlreadyProcessed) { \ + DCOUT("Added shader input connection: " << __name); \ + continue; /* got it */\ + } else if (ret.code == ParseResult::ResultCode::Unmatched) { \ + /* go next */ \ + } else { \ + PUSH_ERROR_AND_RETURN(fmt::format("Parsing shader property `{}` failed. Error: {}", __name, ret.err)); \ + } \ +} + +template +static nonstd::expected CheckAllowedTokens( + const std::vector> &allowedTokens, + const std::string &tok) { + if (allowedTokens.empty()) { + return true; + } + + for (size_t i = 0; i < allowedTokens.size(); i++) { + if (tok.compare(std::get<1>(allowedTokens[i])) == 0) { + return true; + } + } + + std::vector toks; + for (size_t i = 0; i < allowedTokens.size(); i++) { + toks.push_back(std::get<1>(allowedTokens[i])); + } + + std::string s = join(", ", tinyusdz::quote(toks)); + + return nonstd::make_unexpected("Allowed tokens are [" + s + "] but got " + + quote(tok) + "."); +}; + +template +nonstd::expected EnumHandler( + const std::string &prop_name, const std::string &tok, + const std::vector> &enums) { + auto ret = CheckAllowedTokens(enums, tok); + if (!ret) { + return nonstd::make_unexpected(ret.error()); + } + + for (auto &item : enums) { + if (tok == item.second) { + return item.first; + } + } + // Should never reach here, though. + return nonstd::make_unexpected( + quote(tok) + " is an invalid token for attribute `" + prop_name + "`"); +} + + +} // namespace + +// Work around until https://github.com/syoyo/tinyusdz/issues/154 +// clear table to allow the latter attribute can overwrite previous definition. +#define PARSE_TYPED_ATTRIBUTE(__table, __prop, __name, __klass, __target) { \ + ParseResult ret = ParseTypedAttribute(__table, __prop.first, __prop.second, __name, __target); \ + if (ret.code == ParseResult::ResultCode::Success || ret.code == ParseResult::ResultCode::AlreadyProcessed) { \ + /* FIXME: workaround. clear table */ \ + __table.erase(__name); \ + continue; /* got it */\ + } else if (ret.code == ParseResult::ResultCode::Unmatched) { \ + /* go next */ \ + } else { \ + PUSH_ERROR_AND_RETURN(fmt::format("Parsing attribute `{}` failed. Error: {}", __name, ret.err)); \ + } \ +} + +#define PARSE_TYPED_ATTRIBUTE_NOCONTINUE(__table, __prop, __name, __klass, __target) { \ + ParseResult ret = ParseTypedAttribute(__table, __prop.first, __prop.second, __name, __target); \ + if (ret.code == ParseResult::ResultCode::Success || ret.code == ParseResult::ResultCode::AlreadyProcessed) { \ + /* do nothing */ \ + } else if (ret.code == ParseResult::ResultCode::Unmatched) { \ + /* go next */ \ + } else { \ + PUSH_ERROR_AND_RETURN(fmt::format("Parsing attribute `{}` failed. Error: {}", __name, ret.err)); \ + } \ +} + +#define PARSE_EXTENT_ATTRIBUTE(__table, __prop, __name, __klass, __target) { \ + ParseResult ret = ParseExtentAttribute(__table, __prop.first, __prop.second, __name, __target); \ + if (ret.code == ParseResult::ResultCode::Success || ret.code == ParseResult::ResultCode::AlreadyProcessed) { \ + continue; /* got it */\ + } else if (ret.code == ParseResult::ResultCode::Unmatched) { \ + /* go next */ \ + } else { \ + PUSH_ERROR_AND_RETURN(fmt::format("Parsing attribute `extent` failed. Error: {}", ret.err)); \ + } \ +} + +template +using EnumHandlerFun = std::function( + const std::string &)>; + +static nonstd::expected AxisEnumHandler(const std::string &tok) { + using EnumTy = std::pair; + const std::vector enums = { + std::make_pair(Axis::X, "X"), + std::make_pair(Axis::Y, + "Y"), + std::make_pair(Axis::Z, "Z"), + }; + return EnumHandler("axis", tok, enums); +}; + +static nonstd::expected VisibilityEnumHandler(const std::string &tok) { + using EnumTy = std::pair; + const std::vector enums = { + std::make_pair(Visibility::Inherited, "inherited"), + std::make_pair(Visibility::Invisible, "invisible"), + }; + return EnumHandler(kVisibility, tok, enums); +}; + +static nonstd::expected PurposeEnumHandler(const std::string &tok) { + using EnumTy = std::pair; + const std::vector enums = { + std::make_pair(Purpose::Default, "default"), + std::make_pair(Purpose::Proxy, "proxy"), + std::make_pair(Purpose::Render, "render"), + std::make_pair(Purpose::Guide, "guide"), + }; + return EnumHandler("purpose", tok, enums); +}; + +static nonstd::expected OrientationEnumHandler(const std::string &tok) { + using EnumTy = std::pair; + const std::vector enums = { + std::make_pair(Orientation::RightHanded, "rightHanded"), + std::make_pair(Orientation::LeftHanded, "leftHanded"), + }; + return EnumHandler("orientation", tok, enums); +}; + +#if 1 + +template +bool ParseUniformEnumProperty( + const std::string &prop_name, + bool strict_allowedToken_check, + EnumHandlerFun enum_handler, + const Attribute &attr, + TypedAttributeWithFallback *result, + std::string *warn = nullptr, + std::string *err = nullptr) +{ + + if (!result) { + PUSH_ERROR_AND_RETURN("[Internal error] `result` arg is nullptr."); + } + + if (attr.is_connection()) { + PUSH_ERROR_AND_RETURN_F("Attribute connection is not supported in TinyUSDZ for built-in 'enum' token attribute: {}", prop_name); + } + + + if (attr.variability() == Variability::Uniform) { + // scalar + + if (attr.is_blocked()) { + result->set_blocked(true); + return true; + } + + if (attr.get_var().is_timesamples()) { + PUSH_ERROR_AND_RETURN_F("Attribute `{}` is defined as `uniform` variability but TimeSample value is assigned.", prop_name); + } + + if (auto tok = attr.get_value()) { + auto e = enum_handler(tok.value().str()); + if (e) { + (*result) = e.value(); + return true; + } else if (strict_allowedToken_check) { + PUSH_ERROR_AND_RETURN_F("Attribute `{}`: `{}` is not an allowed token.", prop_name, tok.value().str()); + } else { + PUSH_WARN_F("Attribute `{}`: `{}` is not an allowed token. Ignore it.", prop_name, tok.value().str()); + result->set_value_empty(); + return true; + } + } else { + PUSH_ERROR_AND_RETURN_F("Internal error. Maybe type mismatch? Attribute `{}` must be type `token`, but got type `{}`", prop_name, attr.type_name()); + } + + + } else { + // uniform or TimeSamples + if (attr.get_var().is_scalar()) { + + if (attr.is_blocked()) { + result->set_blocked(true); + return true; + } + + if (auto tok = attr.get_value()) { + auto e = enum_handler(tok.value().str()); + if (e) { + (*result) = e.value(); + return true; + } else if (strict_allowedToken_check) { + PUSH_ERROR_AND_RETURN_F("Attribute `{}`: `{}` is not an allowed token.", prop_name, tok.value().str()); + } else { + PUSH_WARN_F("Attribute `{}`: `{}` is not an allowed token. Ignore it.", prop_name, tok.value().str()); + result->set_value_empty(); + return true; + } + } else { + PUSH_ERROR_AND_RETURN_F("Internal error. Maybe type mismatch? Attribute `{}` must be type `token`, but got type `{}`", prop_name, attr.type_name()); + } + } else if (attr.get_var().is_timesamples()) { + PUSH_ERROR_AND_RETURN_F("Attribute `{}` is uniform variability, but TimeSampled value is authored.", + prop_name); + + } else { + PUSH_ERROR_AND_RETURN_F("Internal error. Attribute `{}` is invalid", prop_name); + } + + } + + return false; +} + +// Animatable enum tokens +template +bool ParseTimeSampledEnumProperty( + const std::string &prop_name, + bool strict_allowedToken_check, + EnumHandlerFun enum_handler, + const Attribute &attr, + TypedAttributeWithFallback> *result, + std::string *warn = nullptr, + std::string *err = nullptr) +{ + + if (!result) { + PUSH_ERROR_AND_RETURN("[Internal error] `result` arg is nullptr."); + } + + if (attr.is_connection()) { + PUSH_ERROR_AND_RETURN_F("Attribute connection is not supported in TinyUSDZ for built-in 'enum' token attribute: {}", prop_name); + } + + + if (attr.variability() == Variability::Uniform) { + // scalar + + if (attr.is_blocked()) { + result->set_blocked(true); + return true; + } + + if (attr.get_var().is_timesamples()) { + PUSH_ERROR_AND_RETURN_F("Attribute `{}` is defined as `uniform` variability but TimeSample value is assigned.", prop_name); + } + + if (auto tok = attr.get_value()) { + auto e = enum_handler(tok.value().str()); + if (e) { + (*result) = e.value(); + return true; + } else if (strict_allowedToken_check) { + PUSH_ERROR_AND_RETURN_F("Attribute `{}`: `{}` is not an allowed token.", prop_name, tok.value().str()); + } else { + PUSH_WARN_F("Attribute `{}`: `{}` is not an allowed token. Ignore it.", prop_name, tok.value().str()); + result->set_value_empty(); + return true; + } + } else { + PUSH_ERROR_AND_RETURN_F("Internal error. Maybe type mismatch? Attribute `{}` must be type `token`, but got type `{}`", prop_name, attr.type_name()); + } + + + } else { + // uniform or TimeSamples + if (attr.get_var().is_scalar()) { + + if (attr.is_blocked()) { + result->set_blocked(true); + return true; + } + + if (auto tok = attr.get_value()) { + auto e = enum_handler(tok.value().str()); + if (e) { + (*result) = e.value(); + return true; + } else if (strict_allowedToken_check) { + PUSH_ERROR_AND_RETURN_F("Attribute `{}`: `{}` is not an allowed token.", prop_name, tok.value().str()); + } else { + PUSH_WARN_F("Attribute `{}`: `{}` is not an allowed token. Ignore it.", prop_name, tok.value().str()); + result->set_value_empty(); + return true; + } + } else { + PUSH_ERROR_AND_RETURN_F("Internal error. Maybe type mismatch? Attribute `{}` must be type `token`, but got type `{}`", prop_name, attr.type_name()); + } + } else if (attr.get_var().is_timesamples()) { + size_t n = attr.get_var().num_timesamples(); + + Animatable samples; + + for (size_t i = 0; i < n; i++) { + + double sample_time{value::TimeCode::Default()}; + + if (auto pv = attr.get_var().get_ts_time(i)) { + sample_time = pv.value(); + } else { + // This should not happen. + PUSH_ERROR_AND_RETURN_F("Internal error. Failed to get timecode for `{}`", prop_name); + } + + if (auto pv = attr.get_var().is_ts_value_blocked(i)) { + if (pv.value() == true) { + samples.add_blocked_sample(sample_time); + continue; + } + } else { + // This should not happen. + PUSH_ERROR_AND_RETURN_F("Internal error. Failed to get valueblock info for `{}`", prop_name); + } + + if (auto tok = attr.get_var().get_ts_value(i)) { + auto e = enum_handler(tok.value().str()); + if (e) { + samples.add_sample(sample_time, e.value()); + } else if (strict_allowedToken_check) { + PUSH_ERROR_AND_RETURN_F("Attribute `{}`: `{}` is not an allowed token.", prop_name, tok.value().str()); + } else { + PUSH_WARN_F("Attribute `{}`: `{}` at {}'th timesample is not an allowed token. Ignore it.", prop_name, i, tok.value().str()); + continue; + } + } else { + PUSH_ERROR_AND_RETURN_F("Internal error. Maybe type mismatch? Attribute `{}`'s {}'th timesample must be type `token`, but got type `{}`", prop_name, i, attr.type_name()); + } + } + + result->set_value(samples); + return true; + + } else { + PUSH_ERROR_AND_RETURN_F("Internal error. Attribute `{}` is invalid", prop_name); + } + + } + + return false; +} +#endif + + +#if 0 +// TODO: TimeSamples +#define PARSE_ENUM_PROPETY(__table, __prop, __name, __enum_handler, __klass, \ + __target, __strict_check) { \ + if (__prop.first == __name) { \ + if (__table.count(__name)) { continue; } \ + if ((__prop.second.value_type_name() == value::TypeTraits::type_name()) && __prop.second.is_attribute() && __prop.second.is_empty()) { \ + PUSH_WARN("No value assigned to `" << __name << "` token attribute. Set default token value."); \ + /* TODO: attr meta __target.meta = attr.meta; */ \ + __table.insert(__name); \ + } else { \ + const Attribute &attr = __prop.second.get_attribute(); \ + if (auto tok = attr.get_value()) { \ + auto e = __enum_handler(tok.value().str()); \ + if (e) { \ + __target = e.value(); \ + /* TODO: attr meta __target.meta = attr.meta; */ \ + __table.insert(__name); \ + } else if (__strict_check) { \ + PUSH_ERROR_AND_RETURN("(" << value::TypeTraits<__klass>::type_name() \ + << ") " << e.error()); \ + } else { \ + PUSH_WARN("`" << tok.value().str() << "` is not allowed token for `" << __name << "`. Set to default token value."); \ + /* TODO: attr meta __target.meta = attr.meta; */ \ + __table.insert(__name); \ + } \ + } else { \ + PUSH_ERROR_AND_RETURN("(" << value::TypeTraits<__klass>::type_name() \ + << ") Property type mismatch. " << __name \ + << " must be type `token`, but got `" \ + << attr.type_name() << "`."); \ + } \ + } \ + } \ +} +#else +#define PARSE_UNIFORM_ENUM_PROPERTY(__table, __prop, __name, __enum_ty, __enum_handler, __klass, \ + __target, __strict_check) { \ + if (__prop.first == __name) { \ + if (__table.count(__name)) { continue; } \ + if ((__prop.second.value_type_name() == value::TypeTraits::type_name()) && __prop.second.is_attribute() && __prop.second.is_empty()) { \ + PUSH_WARN("No value assigned to `" << __name << "` token attribute. Set default token value."); \ + /* TODO: attr meta __target.meta = attr.meta; */ \ + __table.insert(__name); \ + } else { \ + const Attribute &attr = __prop.second.get_attribute(); \ + std::function(const std::string &)> fun = __enum_handler; \ + if (!ParseUniformEnumProperty(__name, __strict_check, fun, attr, &__target, warn, err)) { \ + return false; \ + } \ + /* copy metas */ \ + __target.metas() = attr.metas(); \ + __table.insert(__name); \ + } \ + } \ +} + +#define PARSE_TIMESAMPLED_ENUM_PROPERTY(__table, __prop, __name, __enum_ty, __enum_handler, __klass, \ + __target, __strict_check) { \ + if (__prop.first == __name) { \ + if (__table.count(__name)) { continue; } \ + if ((__prop.second.value_type_name() == value::TypeTraits::type_name()) && __prop.second.is_attribute() && __prop.second.is_empty()) { \ + PUSH_WARN("No value assigned to `" << __name << "` token attribute. Set default token value."); \ + /* TODO: attr meta __target.meta = attr.meta; */ \ + __table.insert(__name); \ + } else { \ + const Attribute &attr = __prop.second.get_attribute(); \ + std::function(const std::string &)> fun = __enum_handler; \ + if (!ParseTimeSampledEnumProperty(__name, __strict_check, fun, attr, &__target, warn, err)) { \ + return false; \ + } \ + /* copy metas */ \ + __target.metas() = attr.metas(); \ + __table.insert(__name); \ + } \ + } \ +} +#endif + + +// Add custom property(including property with "primvars" prefix) +// Please call this macro after listing up all predefined property with +// `PARSE_PROPERTY` and `PARSE_***_ENUM_PROPERTY` +#define ADD_PROPERTY(__table, __prop, __klass, __dst) { \ + /* Check if the property name is a predefined property */ \ + if (!__table.count(__prop.first)) { \ + DCOUT("custom property added: name = " << __prop.first); \ + __dst[__prop.first] = __prop.second; \ + __table.insert(__prop.first); \ + } \ + } + +// This code path should not be reached though. +#define PARSE_PROPERTY_END_MAKE_ERROR(__table, __prop) { \ + if (!__table.count(__prop.first)) { \ + PUSH_ERROR_AND_RETURN("Unsupported/unimplemented property: " + \ + __prop.first); \ + } \ + } + +// This code path should not be reached though. +#define PARSE_PROPERTY_END_MAKE_WARN(__table, __prop) { \ + if (!__table.count(__prop.first)) { \ + PUSH_WARN("Unsupported/unimplemented property: " + __prop.first); \ + } \ + } + +bool ReconstructXformOpsFromProperties( + const Specifier &spec, + std::set &table, /* inout */ + const std::map &properties, + std::vector *xformOps, + std::string *err) +{ + + if (spec == Specifier::Class) { + // Do not materialize xformOps here. + return true; + } + + + constexpr auto kTranslate = "xformOp:translate"; + constexpr auto kTransform = "xformOp:transform"; + constexpr auto kScale = "xformOp:scale"; + constexpr auto kRotateX = "xformOp:rotateX"; + constexpr auto kRotateY = "xformOp:rotateY"; + constexpr auto kRotateZ = "xformOp:rotateZ"; + constexpr auto kRotateXYZ = "xformOp:rotateXYZ"; + constexpr auto kRotateXZY = "xformOp:rotateXZY"; + constexpr auto kRotateYXZ = "xformOp:rotateYXZ"; + constexpr auto kRotateYZX = "xformOp:rotateYZX"; + constexpr auto kRotateZXY = "xformOp:rotateZXY"; + constexpr auto kRotateZYX = "xformOp:rotateZYX"; + constexpr auto kOrient = "xformOp:orient"; + + // false : no prefix found. + // true : return suffix(first namespace ':' is ommited.). + // - "" for prefix only "xformOp:translate" + // - "blender:pivot" for "xformOp:translate:blender:pivot" + auto SplitXformOpToken = + [](const std::string &s, + const std::string &prefix) -> nonstd::optional { + if (startsWith(s, prefix)) { + if (s.compare(prefix) == 0) { + // prefix only. + return std::string(); // empty suffix + } else { + std::string suffix = removePrefix(s, prefix); + DCOUT("suffix = " << suffix); + if (suffix.length() == 1) { // maybe namespace only. + return nonstd::nullopt; + } + + // remove namespace ':' + if (suffix[0] == ':') { + // ok + suffix.erase(0, 1); + } else { + return nonstd::nullopt; + } + + return std::move(suffix); + } + } + + return nonstd::nullopt; + }; + + // Lookup xform values from `xformOpOrder` + // TODO: TimeSamples, Connection + if (properties.count("xformOpOrder")) { + // array of string + auto prop = properties.at("xformOpOrder"); + + if (prop.is_relationship()) { + PUSH_ERROR_AND_RETURN("Relationship for `xformOpOrder` is not supported."); + } else if (auto pv = + prop.get_attribute().get_value>()) { + + // 'uniform' check + if (prop.get_attribute().variability() != Variability::Uniform) { + PUSH_ERROR_AND_RETURN("`xformOpOrder` must have `uniform` variability."); + } + + for (size_t i = 0; i < pv.value().size(); i++) { + const auto &item = pv.value()[i]; + + XformOp op; + + std::string tok = item.str(); + DCOUT("xformOp token = " << tok); + + if (startsWith(tok, "!resetXformStack!")) { + if (tok.compare("!resetXformStack!") != 0) { + PUSH_ERROR_AND_RETURN( + "`!resetXformStack!` must be defined solely(not to be a prefix " + "to \"xformOp:*\")"); + } + + if (i != 0) { + PUSH_ERROR_AND_RETURN( + "`!resetXformStack!` must appear at the first element of " + "xformOpOrder list."); + } + + op.op_type = XformOp::OpType::ResetXformStack; + xformOps->emplace_back(op); + + // skip looking up property + continue; + } + + if (startsWith(tok, "!invert!")) { + DCOUT("invert!"); + op.inverted = true; + tok = removePrefix(tok, "!invert!"); + DCOUT("tok = " << tok); + } + + auto it = properties.find(tok); + if (it == properties.end()) { + PUSH_ERROR_AND_RETURN("Property `" + tok + "` not found."); + } + if (it->second.is_connection()) { + PUSH_ERROR_AND_RETURN( + "Connection(.connect) of xformOp property is not yet supported: " + "`" + + tok + "`"); + } + const Attribute &attr = it->second.get_attribute(); + + // Check `xformOp` namespace + if (auto xfm = SplitXformOpToken(tok, kTransform)) { + op.op_type = XformOp::OpType::Transform; + op.suffix = xfm.value(); // may contain nested namespaces + + if (attr.get_var().is_timesamples()) { + op.set_timesamples(attr.get_var().ts_raw()); + } else if (auto pvd = attr.get_value()) { + op.set_value(pvd.value()); + } else { + PUSH_ERROR_AND_RETURN( + "`xformOp:transform` must be type `matrix4d`, but got type `" + + attr.type_name() + "`."); + } + + } else if (auto tx = SplitXformOpToken(tok, kTranslate)) { + op.op_type = XformOp::OpType::Translate; + op.suffix = tx.value(); + + if (attr.get_var().is_timesamples()) { + op.set_timesamples(attr.get_var().ts_raw()); + } else if (auto pvd = attr.get_value()) { + op.set_value(pvd.value()); + } else if (auto pvf = attr.get_value()) { + op.set_value(pvf.value()); + } else { + PUSH_ERROR_AND_RETURN( + "`xformOp:translate` must be type `double3` or `float3`, but " + "got type `" + + attr.type_name() + "`."); + } + } else if (auto scale = SplitXformOpToken(tok, kScale)) { + op.op_type = XformOp::OpType::Scale; + op.suffix = scale.value(); + + if (attr.get_var().is_timesamples()) { + op.set_timesamples(attr.get_var().ts_raw()); + } else if (auto pvd = attr.get_value()) { + op.set_value(pvd.value()); + } else if (auto pvf = attr.get_value()) { + op.set_value(pvf.value()); + } else { + PUSH_ERROR_AND_RETURN( + "`xformOp:scale` must be type `double3` or `float3`, but got " + "type `" + + attr.type_name() + "`."); + } + } else if (auto rotX = SplitXformOpToken(tok, kRotateX)) { + op.op_type = XformOp::OpType::RotateX; + op.suffix = rotX.value(); + + if (attr.get_var().is_timesamples()) { + op.set_timesamples(attr.get_var().ts_raw()); + } else if (auto pvd = attr.get_value()) { + op.set_value(pvd.value()); + } else if (auto pvf = attr.get_value()) { + op.set_value(pvf.value()); + } else { + PUSH_ERROR_AND_RETURN( + "`xformOp:rotateX` must be type `double` or `float`, but got " + "type `" + + attr.type_name() + "`."); + } + } else if (auto rotY = SplitXformOpToken(tok, kRotateY)) { + op.op_type = XformOp::OpType::RotateY; + op.suffix = rotY.value(); + + if (attr.get_var().is_timesamples()) { + op.set_timesamples(attr.get_var().ts_raw()); + } else if (auto pvd = attr.get_value()) { + op.set_value(pvd.value()); + } else if (auto pvf = attr.get_value()) { + op.set_value(pvf.value()); + } else { + PUSH_ERROR_AND_RETURN( + "`xformOp:rotateY` must be type `double` or `float`, but got " + "type `" + + attr.type_name() + "`."); + } + } else if (auto rotZ = SplitXformOpToken(tok, kRotateZ)) { + op.op_type = XformOp::OpType::RotateY; + op.suffix = rotZ.value(); + + if (attr.get_var().is_timesamples()) { + op.set_timesamples(attr.get_var().ts_raw()); + } else if (auto pvd = attr.get_value()) { + op.set_value(pvd.value()); + } else if (auto pvf = attr.get_value()) { + op.set_value(pvf.value()); + } else { + PUSH_ERROR_AND_RETURN( + "`xformOp:rotateZ` must be type `double` or `float`, but got " + "type `" + + attr.type_name() + "`."); + } + } else if (auto rotateXYZ = SplitXformOpToken(tok, kRotateXYZ)) { + op.op_type = XformOp::OpType::RotateXYZ; + op.suffix = rotateXYZ.value(); + + if (attr.get_var().is_timesamples()) { + op.set_timesamples(attr.get_var().ts_raw()); + } else if (auto pvd = attr.get_value()) { + op.set_value(pvd.value()); + } else if (auto pvf = attr.get_value()) { + op.set_value(pvf.value()); + } else { + PUSH_ERROR_AND_RETURN( + "`xformOp:rotateXYZ` must be type `double3` or `float3`, but got " + "type `" + + attr.type_name() + "`."); + } + } else if (auto rotateXZY = SplitXformOpToken(tok, kRotateXZY)) { + op.op_type = XformOp::OpType::RotateXZY; + op.suffix = rotateXZY.value(); + + if (attr.get_var().is_timesamples()) { + op.set_timesamples(attr.get_var().ts_raw()); + } else if (auto pvd = attr.get_value()) { + op.set_value(pvd.value()); + } else if (auto pvf = attr.get_value()) { + op.set_value(pvf.value()); + } else { + PUSH_ERROR_AND_RETURN( + "`xformOp:rotateXZY` must be type `double3` or `float3`, but got " + "type `" + + attr.type_name() + "`."); + } + } else if (auto rotateYXZ = SplitXformOpToken(tok, kRotateYXZ)) { + op.op_type = XformOp::OpType::RotateYXZ; + op.suffix = rotateYXZ.value(); + + if (attr.get_var().is_timesamples()) { + op.set_timesamples(attr.get_var().ts_raw()); + } else if (auto pvd = attr.get_value()) { + op.set_value(pvd.value()); + } else if (auto pvf = attr.get_value()) { + op.set_value(pvf.value()); + } else { + PUSH_ERROR_AND_RETURN( + "`xformOp:rotateYXZ` must be type `double3` or `float3`, but got " + "type `" + + attr.type_name() + "`."); + } + } else if (auto rotateYZX = SplitXformOpToken(tok, kRotateYZX)) { + op.op_type = XformOp::OpType::RotateYZX; + op.suffix = rotateYZX.value(); + + if (attr.get_var().is_timesamples()) { + op.set_timesamples(attr.get_var().ts_raw()); + } else if (auto pvd = attr.get_value()) { + op.set_value(pvd.value()); + } else if (auto pvf = attr.get_value()) { + op.set_value(pvf.value()); + } else { + PUSH_ERROR_AND_RETURN( + "`xformOp:rotateYZX` must be type `double3` or `float3`, but got " + "type `" + + attr.type_name() + "`."); + } + } else if (auto rotateZXY = SplitXformOpToken(tok, kRotateZXY)) { + op.op_type = XformOp::OpType::RotateZXY; + op.suffix = rotateZXY.value(); + + if (attr.get_var().is_timesamples()) { + op.set_timesamples(attr.get_var().ts_raw()); + } else if (auto pvd = attr.get_value()) { + op.set_value(pvd.value()); + } else if (auto pvf = attr.get_value()) { + op.set_value(pvf.value()); + } else { + PUSH_ERROR_AND_RETURN( + "`xformOp:rotateZXY` must be type `double3` or `float3`, but got " + "type `" + + attr.type_name() + "`."); + } + } else if (auto rotateZYX = SplitXformOpToken(tok, kRotateZYX)) { + op.op_type = XformOp::OpType::RotateZYX; + op.suffix = rotateZYX.value(); + + if (attr.get_var().is_timesamples()) { + op.set_timesamples(attr.get_var().ts_raw()); + } else if (auto pvd = attr.get_value()) { + op.set_value(pvd.value()); + } else if (auto pvf = attr.get_value()) { + op.set_value(pvf.value()); + } else { + PUSH_ERROR_AND_RETURN( + "`xformOp:rotateZYX` must be type `double3` or `float3`, but got " + "type `" + + attr.type_name() + "`."); + } + } else if (auto orient = SplitXformOpToken(tok, kOrient)) { + op.op_type = XformOp::OpType::Orient; + op.suffix = orient.value(); + + if (attr.get_var().is_timesamples()) { + op.set_timesamples(attr.get_var().ts_raw()); + } else if (auto pvd = attr.get_value()) { + op.set_value(pvd.value()); + } else if (auto pvf = attr.get_value()) { + op.set_value(pvf.value()); + } else { + PUSH_ERROR_AND_RETURN( + "`xformOp:orient` must be type `quatf` or `quatd`, but got " + "type `" + + attr.type_name() + "`."); + } + } else { + PUSH_ERROR_AND_RETURN( + "token for xformOpOrder must have namespace `xformOp:***`, or ."); + } + + xformOps->emplace_back(op); + table.insert(tok); + } + + } else { + PUSH_ERROR_AND_RETURN( + "`xformOpOrder` must be type `token[]` but got type `" + << prop.get_attribute().type_name() << "`."); + } + } + + table.insert("xformOpOrder"); + return true; +} + +namespace { + +bool ReconstructMaterialBindingProperties( + std::set &table, /* inout */ + const std::map &properties, + MaterialBinding *mb, /* inout */ + std::string *err) +{ + + if (!mb) { + return false; + } + + for (const auto &prop : properties) { + PARSE_SINGLE_TARGET_PATH_RELATION(table, prop, kMaterialBinding, mb->materialBinding) + PARSE_SINGLE_TARGET_PATH_RELATION(table, prop, kMaterialBindingPreview, mb->materialBindingPreview) + PARSE_SINGLE_TARGET_PATH_RELATION(table, prop, kMaterialBindingPreview, mb->materialBindingFull) + // material:binding:collection + if (prop.first == kMaterialBindingCollection) { + + if (table.count(prop.first)) { + continue; + } + + if (!prop.second.is_relationship()) { + PUSH_ERROR_AND_RETURN(fmt::format("`{}` must be a Relationship", prop.first)); + } + + const Relationship &rel = prop.second.get_relationship(); + + mb->set_materialBindingCollection(value::token(""), value::token(""), rel); + + table.insert(prop.first); + continue; + } + // material:binding:collection[:PURPOSE]:NAME + if (startsWith(prop.first, kMaterialBindingCollection + std::string(":"))) { + + if (table.count(prop.first)) { + continue; + } + + if (!prop.second.is_relationship()) { + PUSH_ERROR_AND_RETURN(fmt::format("`{}` must be a Relationship", prop.first)); + } + + std::string collection_name = removePrefix(prop.first, kMaterialBindingCollection + std::string(":")); + if (collection_name.empty()) { + PUSH_ERROR_AND_RETURN("empty NAME is not allowed for 'mateirial:binding:collection'"); + } + std::vector names = split(collection_name, ":"); + if (names.size() > 2) { + PUSH_ERROR_AND_RETURN("3 or more namespaces is not allowed for 'mateirial:binding:collection'"); + } + value::token mat_purpose; // empty = all-purpose + if (names.size() == 1) { + collection_name = names[0]; + } else { + mat_purpose = value::token(names[0]); + collection_name = names[1]; + } + + const Relationship &rel = prop.second.get_relationship(); + + mb->set_materialBindingCollection(value::token(collection_name), mat_purpose, rel); + + table.insert(prop.first); + continue; + } + // material:binding:PURPOSE + if (startsWith(prop.first, kMaterialBinding + std::string(":"))) { + + if (table.count(prop.first)) { + continue; + } + + if (!prop.second.is_relationship()) { + PUSH_ERROR_AND_RETURN(fmt::format("`{}` must be a Relationship", prop.first)); + } + + std::string purpose_name = removePrefix(prop.first, kMaterialBinding + std::string(":")); + if (purpose_name.empty()) { + PUSH_ERROR_AND_RETURN("empty PURPOSE is not allowed for 'mateirial:binding:'"); + } + std::vector names = split(purpose_name, ":"); + if (names.size() > 1) { + PUSH_ERROR_AND_RETURN(fmt::format("PURPOSE `{}` must not have nested namespaces for 'mateirial:binding'", purpose_name)); + } + value::token mat_purpose = value::token(names[0]); + + const Relationship &rel = prop.second.get_relationship(); + + mb->set_materialBinding(rel, mat_purpose); + + table.insert(prop.first); + continue; + } + } + + return true; +} + +bool ReconstructCollectionProperties( + std::set &table, /* inout */ + const std::map &properties, + Collection *coll, /* inout */ + std::string *warn, + std::string *err, + bool strict_allowedToken_check) +{ + constexpr auto kCollectionPrefix = "collection:"; + + std::function(const std::string &)> ExpansionRuleEnumHandler = [](const std::string &tok) { + //auto ExpansionRuleEnumHandler = [](const std::string &tok) { + using EnumTy = std::pair; + const std::vector enums = { + std::make_pair(CollectionInstance::ExpansionRule::ExplicitOnly, kExplicitOnly), + std::make_pair(CollectionInstance::ExpansionRule::ExpandPrims, kExpandPrims), + std::make_pair(CollectionInstance::ExpansionRule::ExpandPrimsAndProperties, kExpandPrimsAndProperties), + }; + return EnumHandler("expansionRule", tok, enums); + }; + + if (!coll) { + return false; + } + + for (const auto &prop : properties) { + if (startsWith(prop.first, kCollectionPrefix)) { + if (table.count(prop.first)) { + continue; + } + + std::string suffix = removePrefix(prop.first, kCollectionPrefix); + std::vector names = split(suffix, ":"); + if (names.size() != 2) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid collection property name. Must be 'collection:INSTANCE_NAME:' but got '{}'", prop.first)); + } + if (names[0].empty()) { + PUSH_ERROR_AND_RETURN("INSTANCE_NAME is empty for collection property name"); + } + if (names[1].empty()) { + PUSH_ERROR_AND_RETURN("Collection property name is empty"); + } + + std::string instance_name = names[0]; + + if (names[1] == "includes") { + + if (!prop.second.is_relationship()) { + PUSH_ERROR_AND_RETURN(fmt::format("`{}` must be a Relationship", prop.first)); + } + + CollectionInstance &coll_instance = coll->get_or_add_instance(instance_name); + coll_instance.includes = prop.second.get_relationship(); + table.insert(prop.first); + + } else if (names[1] == "expansionRule") { + + TypedAttributeWithFallback r{CollectionInstance::ExpansionRule::ExpandPrims}; + + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, prop.first, CollectionInstance::ExpansionRule, ExpansionRuleEnumHandler, CollectionInstance, + r, strict_allowedToken_check) + + if (table.count(prop.first)) { + CollectionInstance &coll_instance = coll->get_or_add_instance(instance_name); + coll_instance.expansionRule = r.get_value(); + } + } else if (names[1] == "includeRoot") { + + TypedAttributeWithFallback> includeRoot{false}; + PARSE_TYPED_ATTRIBUTE_NOCONTINUE(table, prop, prop.first, CollectionInstance, includeRoot) + + if (table.count(prop.first)) { + CollectionInstance &coll_instance = coll->get_or_add_instance(instance_name); + coll_instance.includeRoot = includeRoot; + } + } else if (names[1] == "excludes") { + + if (!prop.second.is_relationship()) { + PUSH_ERROR_AND_RETURN(fmt::format("`{}` must be a Relationship", prop.first)); + } + + CollectionInstance &coll_instance = coll->get_or_add_instance(instance_name); + coll_instance.excludes = prop.second.get_relationship(); + table.insert(prop.first); + + } + } + } + + return true; +} +// xformOps and built-in props +bool ReconstructGPrimProperties( + const Specifier &spec, + std::set &table, /* inout */ + const std::map &properties, + GPrim *gprim, /* inout */ + std::string *warn, + std::string *err, + bool strict_allowedToken_check) +{ + + (void)warn; + if (!prim::ReconstructXformOpsFromProperties(spec, table, properties, &gprim->xformOps, err)) { + return false; + } + + if (!prim::ReconstructMaterialBindingProperties(table, properties, gprim, err)) { + return false; + } + + if (!prim::ReconstructCollectionProperties( + table, properties, gprim, warn, err, strict_allowedToken_check)) { + return false; + } + + for (const auto &prop : properties) { + PARSE_SINGLE_TARGET_PATH_RELATION(table, prop, kProxyPrim, gprim->proxyPrim) + PARSE_TYPED_ATTRIBUTE(table, prop, "doubleSided", GPrim, gprim->doubleSided) + PARSE_TIMESAMPLED_ENUM_PROPERTY(table, prop, kVisibility, Visibility, VisibilityEnumHandler, GPrim, + gprim->visibility, strict_allowedToken_check) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, "purpose", Purpose, PurposeEnumHandler, GPrim, + gprim->purpose, strict_allowedToken_check) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, "orientation", Orientation, OrientationEnumHandler, GPrim, + gprim->orientation, strict_allowedToken_check) + PARSE_EXTENT_ATTRIBUTE(table, prop, "extent", GPrim, gprim->extent) + } + + return true; +} + +} // namespace local + + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + Xform *xform, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)options; + (void)references; + + std::set table; + if (!ReconstructGPrimProperties(spec, table, properties, xform, warn, err, options.strict_allowedToken_check)) { + return false; + } + + for (const auto &prop : properties) { + ADD_PROPERTY(table, prop, Xform, xform->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + Model *model, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + DCOUT("Model "); + (void)spec; + (void)references; + (void)model; + (void)err; + (void)options; + + std::set table; + for (const auto &prop : properties) { + ADD_PROPERTY(table, prop, Model, model->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + Scope *scope, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + // `Scope` is just a namespace in scene graph(no node xform) + + (void)spec; + (void)references; + (void)scope; + (void)err; + (void)options; + + DCOUT("Scope"); + std::set table; + for (const auto &prop : properties) { + PARSE_TIMESAMPLED_ENUM_PROPERTY(table, prop, kVisibility, Visibility, VisibilityEnumHandler, Scope, + scope->visibility, options.strict_allowedToken_check) + ADD_PROPERTY(table, prop, Scope, scope->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + SkelRoot *root, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)references; + (void)options; + + std::set table; + if (!prim::ReconstructXformOpsFromProperties(spec, table, properties, &root->xformOps, err)) { + return false; + } + + // SkelRoot is something like a grouping node, having 1 Skeleton and possibly? + // multiple Prim hierarchy containing GeomMesh. + // No specific properties for SkelRoot(AFAIK) + + // custom props only + for (const auto &prop : properties) { + ADD_PROPERTY(table, prop, SkelRoot, root->props) + PARSE_TIMESAMPLED_ENUM_PROPERTY(table, prop, kVisibility, Visibility, VisibilityEnumHandler, SkelRoot, + root->visibility, options.strict_allowedToken_check) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, kPurpose, Purpose, PurposeEnumHandler, SkelRoot, + root->purpose, options.strict_allowedToken_check) + PARSE_EXTENT_ATTRIBUTE(table, prop, kExtent, SkelRoot, root->extent) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + Skeleton *skel, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)warn; + (void)references; + (void)options; + + std::set table; + if (!prim::ReconstructXformOpsFromProperties(spec, table, properties, &skel->xformOps, err)) { + return false; + } + + for (auto &prop : properties) { + + // SkelBindingAPI + if (prop.first == kSkelAnimationSource) { + + // Must be relation of type Path. + if (prop.second.is_relationship() && prop.second.get_relationship().is_path()) { + { + const Relationship &rel = prop.second.get_relationship(); + if (rel.is_path()) { + skel->animationSource = rel; + table.insert(kSkelAnimationSource); + } else { + PUSH_ERROR_AND_RETURN("`" << kSkelAnimationSource << "` target must be Path."); + } + } + } else { + PUSH_ERROR_AND_RETURN( + "`" << kSkelAnimationSource << "` must be a Relationship with Path target."); + } + } + + // + + PARSE_TYPED_ATTRIBUTE(table, prop, "bindTransforms", Skeleton, skel->bindTransforms) + PARSE_TYPED_ATTRIBUTE(table, prop, "joints", Skeleton, skel->joints) + PARSE_TYPED_ATTRIBUTE(table, prop, "jointNames", Skeleton, skel->jointNames) + PARSE_TYPED_ATTRIBUTE(table, prop, "restTransforms", Skeleton, skel->restTransforms) + PARSE_TIMESAMPLED_ENUM_PROPERTY(table, prop, kVisibility, Visibility, VisibilityEnumHandler, Skeleton, + skel->visibility, options.strict_allowedToken_check) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, "purpose", Purpose, PurposeEnumHandler, Skeleton, + skel->purpose, options.strict_allowedToken_check) + PARSE_EXTENT_ATTRIBUTE(table, prop, "extent", Skeleton, skel->extent) + ADD_PROPERTY(table, prop, Skeleton, skel->props) + PARSE_PROPERTY_END_MAKE_ERROR(table, prop) + } + +#if 0 // TODO: bindTransforms & restTransforms check somewhere. + // usdview and Houdini USD importer expects both `bindTransforms` and `restTransforms` are authored in USD + if (!table.count("bindTransforms")) { + // usdview and Houdini allow `bindTransforms` is not authord in USD, but it cannot compute skinning correctly without it, + // so report an error in TinyUSDZ for a while. + PUSH_ERROR_AND_RETURN_TAG(kTag, "`bindTransforms` is missing in Skeleton. Currently TinyUSDZ expects `bindTransforms` must exist in Skeleton."); + } + + if (!table.count("restTransforms")) { + // usdview and Houdini allow `restTransforms` is not authord in USD(usdview warns it), but it cannot compute skinning correctly without it, + // (even SkelAnimation supplies trasnforms for all joints) + // so report an error in TinyUSDZ for a while. + PUSH_ERROR_AND_RETURN_TAG(kTag, "`restTransforms`(local joint matrices at rest state) is missing in Skeleton. Currently TinyUSDZ expects `restTransforms` must exist in Skeleton."); + } + + // len(bindTransforms) must be equal to len(restTransforms) + // TODO: Support connection + { + bool valid = false; + if (auto bt = skel->bindTransforms.get_value()) { + if (auto rt = skel->restTransforms.get_value()) { + if (bt.value().size() == rt.value().size()) { + // ok + valid = true; + } + } + } + + if (!valid) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Array length must be same for `bindTransforms` and `restTransforms`."); + } + } +#endif + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + SkelAnimation *skelanim, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)spec; + (void)warn; + (void)references; + (void)options; + std::set table; + for (auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, "joints", SkelAnimation, skelanim->joints) + PARSE_TYPED_ATTRIBUTE(table, prop, "translations", SkelAnimation, skelanim->translations) + PARSE_TYPED_ATTRIBUTE(table, prop, "rotations", SkelAnimation, skelanim->rotations) + PARSE_TYPED_ATTRIBUTE(table, prop, "scales", SkelAnimation, skelanim->scales) + PARSE_TYPED_ATTRIBUTE(table, prop, "blendShapes", SkelAnimation, skelanim->blendShapes) + PARSE_TYPED_ATTRIBUTE(table, prop, "blendShapeWeights", SkelAnimation, skelanim->blendShapeWeights) + ADD_PROPERTY(table, prop, Skeleton, skelanim->props) + PARSE_PROPERTY_END_MAKE_ERROR(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + BlendShape *bs, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + (void)spec; + (void)warn; + (void)references; + (void)options; + + DCOUT("Reconstruct BlendShape"); + + constexpr auto kOffsets = "offsets"; + constexpr auto kNormalOffsets = "normalOffsets"; + constexpr auto kPointIndices = "pointIndices"; + + std::set table; + for (auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, kOffsets, BlendShape, bs->offsets) + PARSE_TYPED_ATTRIBUTE(table, prop, kNormalOffsets, BlendShape, bs->normalOffsets) + PARSE_TYPED_ATTRIBUTE(table, prop, kPointIndices, BlendShape, bs->pointIndices) + ADD_PROPERTY(table, prop, Skeleton, bs->props) + PARSE_PROPERTY_END_MAKE_ERROR(table, prop) + } + +#if 0 // TODO: Check required properties exist in strict mode. + // `offsets` and `normalOffsets` are required property + if (!table.count(kOffsets)) { + PUSH_ERROR_AND_RETURN("`offsets` property is missing. `uniform vector3f[] offsets` is a required property."); + } + if (!table.count(kNormalOffsets)) { + PUSH_ERROR_AND_RETURN("`normalOffsets` property is missing. `uniform vector3f[] normalOffsets` is a required property."); + } +#endif + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + GPrim *gprim, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + (void)gprim; + (void)err; + + (void)references; + (void)properties; + + std::set table; + if (!ReconstructGPrimProperties(spec, table, properties, gprim, warn, err, options.strict_allowedToken_check)) { + return false; + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + GeomBasisCurves *curves, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + (void)references; + (void)options; + + DCOUT("GeomBasisCurves"); + + auto BasisHandler = [](const std::string &tok) + -> nonstd::expected { + using EnumTy = std::pair; + const std::vector enums = { + std::make_pair(GeomBasisCurves::Basis::Bezier, "bezier"), + std::make_pair(GeomBasisCurves::Basis::Bspline, "bspline"), + std::make_pair(GeomBasisCurves::Basis::CatmullRom, "catmullRom"), + }; + + return EnumHandler("basis", tok, enums); + }; + + auto TypeHandler = [](const std::string &tok) + -> nonstd::expected { + using EnumTy = std::pair; + const std::vector enums = { + std::make_pair(GeomBasisCurves::Type::Cubic, "cubic"), + std::make_pair(GeomBasisCurves::Type::Linear, "linear"), + }; + + return EnumHandler("type", tok, enums); + }; + + auto WrapHandler = [](const std::string &tok) + -> nonstd::expected { + using EnumTy = std::pair; + const std::vector enums = { + std::make_pair(GeomBasisCurves::Wrap::Nonperiodic, "nonperiodic"), + std::make_pair(GeomBasisCurves::Wrap::Periodic, "periodic"), + std::make_pair(GeomBasisCurves::Wrap::Pinned, "periodic"), + }; + + return EnumHandler("wrap", tok, enums); + }; + + std::set table; + if (!ReconstructGPrimProperties(spec, table, properties, curves, warn, err, options.strict_allowedToken_check)) { + return false; + } + + for (const auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, "curveVertexCounts", GeomBasisCurves, + curves->curveVertexCounts) + PARSE_TYPED_ATTRIBUTE(table, prop, "points", GeomBasisCurves, curves->points) + PARSE_TYPED_ATTRIBUTE(table, prop, "velocities", GeomBasisCurves, + curves->velocities) + PARSE_TYPED_ATTRIBUTE(table, prop, "normals", GeomBasisCurves, + curves->normals) + PARSE_TYPED_ATTRIBUTE(table, prop, "accelerations", GeomBasisCurves, + curves->accelerations) + PARSE_TYPED_ATTRIBUTE(table, prop, "widths", GeomBasisCurves, curves->widths) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, "type", GeomBasisCurves::Type, TypeHandler, GeomBasisCurves, + curves->type, options.strict_allowedToken_check) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, "basis", GeomBasisCurves::Basis, BasisHandler, GeomBasisCurves, + curves->basis, options.strict_allowedToken_check) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, "wrap", GeomBasisCurves::Wrap, WrapHandler, GeomBasisCurves, + curves->wrap, options.strict_allowedToken_check) + + ADD_PROPERTY(table, prop, GeomBasisCurves, curves->props) + + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + GeomNurbsCurves *curves, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + (void)references; + (void)options; + + std::set table; + if (!ReconstructGPrimProperties(spec, table, properties, curves, warn, err, options.strict_allowedToken_check)) { + return false; + } + + for (const auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, "curveVertexCounts", GeomNurbsCurves, + curves->curveVertexCounts) + PARSE_TYPED_ATTRIBUTE(table, prop, "points", GeomNurbsCurves, curves->points) + PARSE_TYPED_ATTRIBUTE(table, prop, "velocities", GeomNurbsCurves, + curves->velocities) + PARSE_TYPED_ATTRIBUTE(table, prop, "normals", GeomNurbsCurves, + curves->normals) + PARSE_TYPED_ATTRIBUTE(table, prop, "accelerations", GeomNurbsCurves, + curves->accelerations) + PARSE_TYPED_ATTRIBUTE(table, prop, "widths", GeomNurbsCurves, curves->widths) + + // + PARSE_TYPED_ATTRIBUTE(table, prop, "order", GeomNurbsCurves, curves->order) + PARSE_TYPED_ATTRIBUTE(table, prop, "knots", GeomNurbsCurves, curves->knots) + PARSE_TYPED_ATTRIBUTE(table, prop, "ranges", GeomNurbsCurves, curves->ranges) + PARSE_TYPED_ATTRIBUTE(table, prop, "pointWeights", GeomNurbsCurves, curves->pointWeights) + + ADD_PROPERTY(table, prop, GeomBasisCurves, curves->props) + + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + SphereLight *light, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)references; + + (void)options; + std::set table; + + if (!prim::ReconstructXformOpsFromProperties(spec, table, properties, &light->xformOps, err)) { + return false; + } + + for (const auto &prop : properties) { + // PARSE_PROPERTY(prop, "inputs:colorTemperature", light->colorTemperature) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:color", SphereLight, light->color) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:radius", SphereLight, light->radius) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:intensity", SphereLight, + light->intensity) + PARSE_TIMESAMPLED_ENUM_PROPERTY(table, prop, kVisibility, Visibility, VisibilityEnumHandler, SphereLight, + light->visibility, options.strict_allowedToken_check) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, kPurpose, Purpose, PurposeEnumHandler, SphereLight, + light->purpose, options.strict_allowedToken_check) + PARSE_EXTENT_ATTRIBUTE(table, prop, kExtent, SphereLight, light->extent) + ADD_PROPERTY(table, prop, SphereLight, light->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + RectLight *light, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)references; + (void)options; + + std::set table; + + if (!prim::ReconstructXformOpsFromProperties(spec, table, properties, &light->xformOps, err)) { + return false; + } + + for (const auto &prop : properties) { + // PARSE_PROPERTY(prop, "inputs:colorTemperature", light->colorTemperature) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:texture:file", UsdUVTexture, light->file) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:color", RectLight, light->color) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:height", RectLight, light->height) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:width", RectLight, light->width) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:intensity", RectLight, + light->intensity) + PARSE_EXTENT_ATTRIBUTE(table, prop, kExtent, RectLight, light->extent) + PARSE_TIMESAMPLED_ENUM_PROPERTY(table, prop, kVisibility, Visibility, VisibilityEnumHandler, RectLight, + light->visibility, options.strict_allowedToken_check) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, kPurpose, Purpose, PurposeEnumHandler, RectLight, + light->purpose, options.strict_allowedToken_check) + ADD_PROPERTY(table, prop, SphereLight, light->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + DiskLight *light, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)references; + (void)options; + + std::set table; + + if (!prim::ReconstructXformOpsFromProperties(spec, table, properties, &light->xformOps, err)) { + return false; + } + + for (const auto &prop : properties) { + // PARSE_PROPERTY(prop, "inputs:colorTemperature", light->colorTemperature) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:radius", DiskLight, light->radius) + PARSE_EXTENT_ATTRIBUTE(table, prop, kExtent, DiskLight, light->extent) + PARSE_TIMESAMPLED_ENUM_PROPERTY(table, prop, kVisibility, Visibility, VisibilityEnumHandler, DiskLight, + light->visibility, options.strict_allowedToken_check) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, kPurpose, Purpose, PurposeEnumHandler, DiskLight, + light->purpose, options.strict_allowedToken_check) + ADD_PROPERTY(table, prop, DiskLight, light->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + CylinderLight *light, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)references; + (void)options; + + std::set table; + + if (!prim::ReconstructXformOpsFromProperties(spec, table, properties, &light->xformOps, err)) { + return false; + } + + for (const auto &prop : properties) { + // PARSE_PROPERTY(prop, "inputs:colorTemperature", light->colorTemperature) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:length", CylinderLight, light->length) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:radius", CylinderLight, light->radius) + PARSE_EXTENT_ATTRIBUTE(table, prop, kExtent, CylinderLight, light->extent) + PARSE_TIMESAMPLED_ENUM_PROPERTY(table, prop, kVisibility, Visibility, VisibilityEnumHandler, CylindrLight, + light->visibility, options.strict_allowedToken_check) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, kPurpose, Purpose, PurposeEnumHandler, CylinderLight, + light->purpose, options.strict_allowedToken_check) + ADD_PROPERTY(table, prop, SphereLight, light->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + DistantLight *light, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)references; + (void)options; + + std::set table; + + if (!prim::ReconstructXformOpsFromProperties(spec, table, properties, &light->xformOps, err)) { + return false; + } + + for (const auto &prop : properties) { + // PARSE_PROPERTY(prop, "inputs:colorTemperature", light->colorTemperature) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:angle", DistantLight, light->angle) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, kPurpose, Purpose, PurposeEnumHandler, DistantLight, + light->purpose, options.strict_allowedToken_check) + PARSE_TIMESAMPLED_ENUM_PROPERTY(table, prop, kVisibility, Visibility, VisibilityEnumHandler, DistantLight, + light->visibility, options.strict_allowedToken_check) + ADD_PROPERTY(table, prop, SphereLight, light->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + DomeLight *light, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)references; + (void)options; + + std::set table; + + if (!prim::ReconstructXformOpsFromProperties(spec, table, properties, &light->xformOps, err)) { + return false; + } + + for (const auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, "guideRadius", DomeLight, light->guideRadius) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:diffuse", DomeLight, light->diffuse) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:specular", DomeLight, + light->specular) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:colorTemperature", DomeLight, + light->colorTemperature) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:color", DomeLight, light->color) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:intensity", DomeLight, + light->intensity) + PARSE_TIMESAMPLED_ENUM_PROPERTY(table, prop, kVisibility, Visibility, VisibilityEnumHandler, DomeLight, + light->visibility, options.strict_allowedToken_check) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, kPurpose, Purpose, PurposeEnumHandler, DomeLight, + light->purpose, options.strict_allowedToken_check) + ADD_PROPERTY(table, prop, DomeLight, light->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + DCOUT("Implement DomeLight"); + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + GeomSphere *sphere, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)warn; + (void)references; + (void)options; + + DCOUT("Reconstruct Sphere."); + + std::set table; + if (!ReconstructGPrimProperties(spec, table, properties, sphere, warn, err, options.strict_allowedToken_check)) { + return false; + } + + for (const auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, "radius", GeomSphere, sphere->radius) + ADD_PROPERTY(table, prop, GeomSphere, sphere->props) + PARSE_PROPERTY_END_MAKE_ERROR(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + GeomPoints *points, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)warn; + (void)references; + (void)options; + + DCOUT("Reconstruct Points."); + + std::set table; + if (!ReconstructGPrimProperties(spec, table, properties, points, warn, err, options.strict_allowedToken_check)) { + return false; + } + + for (const auto &prop : properties) { + DCOUT("prop: " << prop.first); + PARSE_TYPED_ATTRIBUTE(table, prop, "points", GeomPoints, points->points) + PARSE_TYPED_ATTRIBUTE(table, prop, "normals", GeomPoints, points->normals) + PARSE_TYPED_ATTRIBUTE(table, prop, "widths", GeomPoints, points->widths) + PARSE_TYPED_ATTRIBUTE(table, prop, "ids", GeomPoints, points->ids) + PARSE_TYPED_ATTRIBUTE(table, prop, "velocities", GeomPoints, points->velocities) + PARSE_TYPED_ATTRIBUTE(table, prop, "accelerations", GeomPoints, points->accelerations) + ADD_PROPERTY(table, prop, GeomSphere, points->props) + PARSE_PROPERTY_END_MAKE_ERROR(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + GeomCone *cone, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)warn; + (void)references; + (void)options; + + std::set table; + if (!ReconstructGPrimProperties(spec, table, properties, cone, warn, err, options.strict_allowedToken_check)) { + return false; + } + + for (const auto &prop : properties) { + DCOUT("prop: " << prop.first); + PARSE_TYPED_ATTRIBUTE(table, prop, "radius", GeomCone, cone->radius) + PARSE_TYPED_ATTRIBUTE(table, prop, "height", GeomCone, cone->height) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, "axis", Axis, AxisEnumHandler, GeomCone, cone->axis, options.strict_allowedToken_check) + ADD_PROPERTY(table, prop, GeomCone, cone->props) + PARSE_PROPERTY_END_MAKE_ERROR(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + GeomCylinder *cylinder, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)warn; + (void)references; + (void)options; + + std::set table; + if (!ReconstructGPrimProperties(spec, table, properties, cylinder, warn, err, options.strict_allowedToken_check)) { + return false; + } + + for (const auto &prop : properties) { + DCOUT("prop: " << prop.first); + PARSE_TYPED_ATTRIBUTE(table, prop, "radius", GeomCylinder, + cylinder->radius) + PARSE_TYPED_ATTRIBUTE(table, prop, "height", GeomCylinder, + cylinder->height) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, "axis", Axis, AxisEnumHandler, GeomCylinder, cylinder->axis, options.strict_allowedToken_check) + ADD_PROPERTY(table, prop, GeomCylinder, cylinder->props) + PARSE_PROPERTY_END_MAKE_ERROR(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + GeomCapsule *capsule, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)warn; + (void)references; + (void)options; + + std::set table; + if (!ReconstructGPrimProperties(spec, table, properties, capsule, warn, err, options.strict_allowedToken_check)) { + return false; + } + + for (const auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, "radius", GeomCapsule, capsule->radius) + PARSE_TYPED_ATTRIBUTE(table, prop, "height", GeomCapsule, capsule->height) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, "axis", Axis, AxisEnumHandler, GeomCapsule, capsule->axis, options.strict_allowedToken_check) + ADD_PROPERTY(table, prop, GeomCapsule, capsule->props) + PARSE_PROPERTY_END_MAKE_ERROR(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + GeomCube *cube, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)warn; + (void)references; + (void)options; + + // + // pxrUSD says... "If you author size you must also author extent." + // + std::set table; + if (!ReconstructGPrimProperties(spec, table, properties, cube, warn, err, options.strict_allowedToken_check)) { + return false; + } + + for (const auto &prop : properties) { + DCOUT("prop: " << prop.first); + PARSE_TYPED_ATTRIBUTE(table, prop, "size", GeomCube, cube->size) + ADD_PROPERTY(table, prop, GeomCube, cube->props) + PARSE_PROPERTY_END_MAKE_ERROR(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + GeomMesh *mesh, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)references; + (void)options; + + DCOUT("GeomMesh"); + + auto SubdivisionSchemeHandler = [](const std::string &tok) + -> nonstd::expected { + using EnumTy = std::pair; + const std::vector enums = { + std::make_pair(GeomMesh::SubdivisionScheme::SubdivisionSchemeNone, "none"), + std::make_pair(GeomMesh::SubdivisionScheme::CatmullClark, + "catmullClark"), + std::make_pair(GeomMesh::SubdivisionScheme::Loop, "loop"), + std::make_pair(GeomMesh::SubdivisionScheme::Bilinear, "bilinear"), + }; + return EnumHandler("subdivisionScheme", tok, + enums); + }; + + auto InterpolateBoundaryHandler = [](const std::string &tok) + -> nonstd::expected { + using EnumTy = std::pair; + const std::vector enums = { + std::make_pair(GeomMesh::InterpolateBoundary::InterpolateBoundaryNone, "none"), + std::make_pair(GeomMesh::InterpolateBoundary::EdgeAndCorner, + "edgeAndCorner"), + std::make_pair(GeomMesh::InterpolateBoundary::EdgeOnly, "edgeOnly"), + }; + return EnumHandler("interpolateBoundary", + tok, enums); + }; + + auto FaceVaryingLinearInterpolationHandler = [](const std::string &tok) + -> nonstd::expected { + using EnumTy = + std::pair; + const std::vector enums = { + std::make_pair(GeomMesh::FaceVaryingLinearInterpolation::CornersPlus1, + "cornersPlus1"), + std::make_pair(GeomMesh::FaceVaryingLinearInterpolation::CornersPlus2, + "cornersPlus2"), + std::make_pair(GeomMesh::FaceVaryingLinearInterpolation::CornersOnly, + "cornersOnly"), + std::make_pair(GeomMesh::FaceVaryingLinearInterpolation::Boundaries, + "boundaries"), + std::make_pair(GeomMesh::FaceVaryingLinearInterpolation::FaceVaryingLinearInterpolationNone, "none"), + std::make_pair(GeomMesh::FaceVaryingLinearInterpolation::All, "all"), + }; + return EnumHandler( + "facevaryingLinearInterpolation", tok, enums); + }; + + auto FamilyTypeHandler = [](const std::string &tok) + -> nonstd::expected { + using EnumTy = std::pair; + const std::vector enums = { + std::make_pair(GeomSubset::FamilyType::Partition, "partition"), + std::make_pair(GeomSubset::FamilyType::NonOverlapping, "nonOverlapping"), + std::make_pair(GeomSubset::FamilyType::Unrestricted, "unrestricted"), + }; + return EnumHandler("familyType", tok, + enums); + }; + + std::set table; + if (!ReconstructGPrimProperties(spec, table, properties, mesh, warn, err, options.strict_allowedToken_check)) { + return false; + } + + for (const auto &prop : properties) { + DCOUT("GeomMesh prop: " << prop.first); + PARSE_SINGLE_TARGET_PATH_RELATION(table, prop, kSkelSkeleton, mesh->skeleton) + PARSE_TARGET_PATHS_RELATION(table, prop, kSkelBlendShapeTargets, mesh->blendShapeTargets) + PARSE_TYPED_ATTRIBUTE(table, prop, "points", GeomMesh, mesh->points) + PARSE_TYPED_ATTRIBUTE(table, prop, "normals", GeomMesh, mesh->normals) + PARSE_TYPED_ATTRIBUTE(table, prop, "faceVertexCounts", GeomMesh, + mesh->faceVertexCounts) + PARSE_TYPED_ATTRIBUTE(table, prop, "faceVertexIndices", GeomMesh, + mesh->faceVertexIndices) + // Subd + PARSE_TYPED_ATTRIBUTE(table, prop, "cornerIndices", GeomMesh, + mesh->cornerIndices) + PARSE_TYPED_ATTRIBUTE(table, prop, "cornerSharpnesses", GeomMesh, + mesh->cornerSharpnesses) + PARSE_TYPED_ATTRIBUTE(table, prop, "creaseIndices", GeomMesh, + mesh->creaseIndices) + PARSE_TYPED_ATTRIBUTE(table, prop, "creaseLengths", GeomMesh, + mesh->creaseLengths) + PARSE_TYPED_ATTRIBUTE(table, prop, "creaseSharpnesses", GeomMesh, + mesh->creaseSharpnesses) + PARSE_TYPED_ATTRIBUTE(table, prop, "holeIndices", GeomMesh, + mesh->holeIndices) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, "subdivisionScheme", GeomMesh::SubdivisionScheme, + SubdivisionSchemeHandler, GeomMesh, + mesh->subdivisionScheme, options.strict_allowedToken_check) + PARSE_TIMESAMPLED_ENUM_PROPERTY(table, prop, "interpolateBoundary", + GeomMesh::InterpolateBoundary, InterpolateBoundaryHandler, GeomMesh, + mesh->interpolateBoundary, options.strict_allowedToken_check) + PARSE_TIMESAMPLED_ENUM_PROPERTY(table, prop, "facevaryingLinearInterpolation", + GeomMesh::FaceVaryingLinearInterpolation, FaceVaryingLinearInterpolationHandler, GeomMesh, + mesh->faceVaryingLinearInterpolation, options.strict_allowedToken_check) + // blendShape names + PARSE_TYPED_ATTRIBUTE(table, prop, kSkelBlendShapes, GeomMesh, mesh->blendShapes) + + // subsetFamily for GeomSubset + if (startsWith(prop.first, "subsetFamily")) { + // uniform subsetFamily:::familyType = ... + std::vector names = split(prop.first, ":"); + + if ((names.size() == 3) && + (names[0] == "subsetFamily") && + (names[2] == "familyType")) { + + DCOUT("subsetFamily" << prop.first); + TypedAttributeWithFallback familyType{GeomSubset::FamilyType::Unrestricted}; + + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, prop.first, + GeomSubset::FamilyType, FamilyTypeHandler, GeomMesh, + familyType, options.strict_allowedToken_check) + + // NOTE: Ignore metadataum of familyType. + + // TODO: Validate familyName + mesh->subsetFamilyTypeMap[value::token(names[1])] = familyType.get_value(); + + } + } + + // generic + ADD_PROPERTY(table, prop, GeomMesh, mesh->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + + return true; +} + + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + GeomCamera *camera, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + (void)references; + (void)warn; + (void)options; + + auto ProjectionHandler = [](const std::string &tok) + -> nonstd::expected { + using EnumTy = std::pair; + constexpr std::array enums = { + std::make_pair(GeomCamera::Projection::Perspective, "perspective"), + std::make_pair(GeomCamera::Projection::Orthographic, "orthographic"), + }; + + auto ret = + CheckAllowedTokens(enums, tok); + if (!ret) { + return nonstd::make_unexpected(ret.error()); + } + + for (auto &item : enums) { + if (tok == item.second) { + return item.first; + } + } + + // Should never reach here, though. + return nonstd::make_unexpected( + quote(tok) + " is invalid token for `projection` propety"); + }; + + auto StereoRoleHandler = [](const std::string &tok) + -> nonstd::expected { + using EnumTy = std::pair; + constexpr std::array enums = { + std::make_pair(GeomCamera::StereoRole::Mono, "mono"), + std::make_pair(GeomCamera::StereoRole::Left, "left"), + std::make_pair(GeomCamera::StereoRole::Right, "right"), + }; + + auto ret = + CheckAllowedTokens(enums, tok); + if (!ret) { + return nonstd::make_unexpected(ret.error()); + } + + for (auto &item : enums) { + if (tok == item.second) { + return item.first; + } + } + + // Should never reach here, though. + return nonstd::make_unexpected( + quote(tok) + " is invalid token for `stereoRole` propety"); + }; + + std::set table; + if (!ReconstructGPrimProperties(spec, table, properties, camera, warn, err, options.strict_allowedToken_check)) { + return false; + } + + for (const auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, "focalLength", GeomCamera, camera->focalLength) + PARSE_TYPED_ATTRIBUTE(table, prop, "focusDistance", GeomCamera, + camera->focusDistance) + PARSE_TYPED_ATTRIBUTE(table, prop, "exposure", GeomCamera, camera->exposure) + PARSE_TYPED_ATTRIBUTE(table, prop, "fStop", GeomCamera, camera->fStop) + PARSE_TYPED_ATTRIBUTE(table, prop, "horizontalAperture", GeomCamera, + camera->horizontalAperture) + PARSE_TYPED_ATTRIBUTE(table, prop, "horizontalApertureOffset", GeomCamera, + camera->horizontalApertureOffset) + PARSE_TYPED_ATTRIBUTE(table, prop, "verticalAperture", GeomCamera, + camera->verticalAperture) + PARSE_TYPED_ATTRIBUTE(table, prop, "verticalApertureOffset", GeomCamera, + camera->verticalApertureOffset) + PARSE_TYPED_ATTRIBUTE(table, prop, "clippingRange", GeomCamera, + camera->clippingRange) + PARSE_TYPED_ATTRIBUTE(table, prop, "clippingPlanes", GeomCamera, + camera->clippingPlanes) + PARSE_TYPED_ATTRIBUTE(table, prop, "shutter:open", GeomCamera, camera->shutterOpen) + PARSE_TYPED_ATTRIBUTE(table, prop, "shutter:close", GeomCamera, + camera->shutterClose) + PARSE_TIMESAMPLED_ENUM_PROPERTY(table, prop, "projection", GeomCamera::Projection, ProjectionHandler, GeomCamera, + camera->projection, options.strict_allowedToken_check) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, "stereoRole", GeomCamera::StereoRole, StereoRoleHandler, GeomCamera, + camera->stereoRole, options.strict_allowedToken_check) + ADD_PROPERTY(table, prop, GeomCamera, camera->props) + PARSE_PROPERTY_END_MAKE_ERROR(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + GeomSubset *subset, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)spec; + (void)references; + + DCOUT("GeomSubset"); + + // Currently schema only allows 'face' + auto ElementTypeHandler = [](const std::string &tok) + -> nonstd::expected { + using EnumTy = std::pair; + const std::vector enums = { + std::make_pair(GeomSubset::ElementType::Face, "face"), + std::make_pair(GeomSubset::ElementType::Point, "point"), + }; + return EnumHandler("elementType", tok, + enums); + }; + + std::set table; + + if (!prim::ReconstructMaterialBindingProperties(table, properties, subset, err)) { + return false; + } + + if (!prim::ReconstructCollectionProperties( + table, properties, subset, warn, err, options.strict_allowedToken_check)) { + return false; + } + + for (const auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, "familyName", GeomSubset, subset->familyName) + PARSE_TYPED_ATTRIBUTE(table, prop, "indices", GeomSubset, subset->indices) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, "elementType", GeomSubset::ElementType, ElementTypeHandler, GeomSubset, subset->elementType, options.strict_allowedToken_check) + ADD_PROPERTY(table, prop, GeomSubset, subset->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + PointInstancer *instancer, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + + (void)warn; + (void)references; + (void)options; + + DCOUT("Reconstruct PointInstancer."); + + std::set table; + if (!ReconstructGPrimProperties(spec, table, properties, instancer, warn, err, options.strict_allowedToken_check)) { + return false; + } + + for (const auto &prop : properties) { + PARSE_TARGET_PATHS_RELATION(table, prop, "prototypes", instancer->prototypes) + PARSE_TYPED_ATTRIBUTE(table, prop, "protoIndices", PointInstancer, instancer->protoIndices) + PARSE_TYPED_ATTRIBUTE(table, prop, "ids", PointInstancer, instancer->ids) + PARSE_TYPED_ATTRIBUTE(table, prop, "positions", PointInstancer, instancer->positions) + PARSE_TYPED_ATTRIBUTE(table, prop, "orientations", PointInstancer, instancer->orientations) + PARSE_TYPED_ATTRIBUTE(table, prop, "scales", PointInstancer, instancer->scales) + PARSE_TYPED_ATTRIBUTE(table, prop, "velocities", PointInstancer, instancer->velocities) + PARSE_TYPED_ATTRIBUTE(table, prop, "accelerations", PointInstancer, instancer->accelerations) + PARSE_TYPED_ATTRIBUTE(table, prop, "angularVelocities", PointInstancer, instancer->angularVelocities) + PARSE_TYPED_ATTRIBUTE(table, prop, "invisibleIds", PointInstancer, instancer->invisibleIds) + + ADD_PROPERTY(table, prop, PointInstancer, instancer->props) + PARSE_PROPERTY_END_MAKE_ERROR(table, prop) + } + + return true; +} + +template <> +bool ReconstructShader( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + ShaderNode *node, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) +{ + (void)spec; + (void)options; + + if (!node) { + return false; + } + + // TODO: references + (void)references; + + std::set table; + table.insert("info:id"); // `info:id` is already parsed in ReconstructPrim + + // Add everything to props. + for (auto &prop : properties) { + ADD_PROPERTY(table, prop, ShaderNode, node->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + DCOUT("ShaderNode reconstructed."); + return true; +} + +template <> +bool ReconstructShader( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + UsdPreviewSurface *surface, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) { + (void)spec; + (void)references; + (void)options; + + std::set table; + table.insert("info:id"); // `info:id` is already parsed in ReconstructPrim + for (auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:diffuseColor", UsdPreviewSurface, + surface->diffuseColor) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:emissiveColor", UsdPreviewSurface, + surface->emissiveColor) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:roughness", UsdPreviewSurface, + surface->roughness) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:specularColor", UsdPreviewSurface, + surface->specularColor) // specular workflow + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:metallic", UsdPreviewSurface, + surface->metallic) // non specular workflow + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:clearcoat", UsdPreviewSurface, + surface->clearcoat) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:clearcoatRoughness", + UsdPreviewSurface, surface->clearcoatRoughness) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:opacity", UsdPreviewSurface, + surface->opacity) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:opacityThreshold", + UsdPreviewSurface, surface->opacityThreshold) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:ior", UsdPreviewSurface, + surface->ior) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:normal", UsdPreviewSurface, + surface->normal) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:dispacement", UsdPreviewSurface, + surface->displacement) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:occlusion", UsdPreviewSurface, + surface->occlusion) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:useSpecularWorkflow", + UsdPreviewSurface, surface->useSpecularWorkflow) + PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:surface", UsdPreviewSurface, + surface->outputsSurface) + PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:displacement", UsdPreviewSurface, + surface->outputsDisplacement) + ADD_PROPERTY(table, prop, UsdPreviewSurface, surface->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + return true; +} + +template <> +bool ReconstructShader( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + UsdUVTexture *texture, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) +{ + (void)spec; + (void)references; + (void)options; + + auto SourceColorSpaceHandler = [](const std::string &tok) + -> nonstd::expected { + using EnumTy = std::pair; + const std::vector enums = { + std::make_pair(UsdUVTexture::SourceColorSpace::Auto, "auto"), + std::make_pair(UsdUVTexture::SourceColorSpace::Raw, "raw"), + std::make_pair(UsdUVTexture::SourceColorSpace::SRGB, "sRGB"), + }; + + return EnumHandler( + "inputs:sourceColorSpace", tok, enums); + }; + + auto WrapHandler = [](const std::string &tok) + -> nonstd::expected { + using EnumTy = std::pair; + const std::vector enums = { + std::make_pair(UsdUVTexture::Wrap::UseMetadata, "useMetadata"), + std::make_pair(UsdUVTexture::Wrap::Black, "black"), + std::make_pair(UsdUVTexture::Wrap::Clamp, "clamp"), + std::make_pair(UsdUVTexture::Wrap::Repeat, "repeat"), + std::make_pair(UsdUVTexture::Wrap::Mirror, "mirror"), + }; + + return EnumHandler( + "inputs:wrap*", tok, enums); + }; + + std::set table; + table.insert("info:id"); // `info:id` is already parsed in ReconstructPrim + + for (auto &prop : properties) { + DCOUT("prop.name = " << prop.first); + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:file", UsdUVTexture, texture->file) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:st", UsdUVTexture, + texture->st) + PARSE_TIMESAMPLED_ENUM_PROPERTY(table, prop, "inputs:sourceColorSpace", + UsdUVTexture::SourceColorSpace, SourceColorSpaceHandler, UsdUVTexture, + texture->sourceColorSpace, options.strict_allowedToken_check) + PARSE_TIMESAMPLED_ENUM_PROPERTY(table, prop, "inputs:wrapS", + UsdUVTexture::Wrap, WrapHandler, UsdUVTexture, + texture->wrapS, options.strict_allowedToken_check) + PARSE_TIMESAMPLED_ENUM_PROPERTY(table, prop, "inputs:wrapT", + UsdUVTexture::Wrap, WrapHandler, UsdUVTexture, + texture->wrapT, options.strict_allowedToken_check) + PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:r", UsdUVTexture, + texture->outputsR) + PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:g", UsdUVTexture, + texture->outputsG) + PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:b", UsdUVTexture, + texture->outputsB) + PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:a", UsdUVTexture, + texture->outputsA) + PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:rgb", UsdUVTexture, + texture->outputsRGB) + ADD_PROPERTY(table, prop, UsdUVTexture, texture->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + DCOUT("UsdUVTexture reconstructed."); + return true; +} + +template <> +bool ReconstructShader( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + UsdPrimvarReader_int *preader, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) +{ + (void)spec; + (void)references; + (void)options; + std::set table; + table.insert("info:id"); // `info:id` is already parsed in ReconstructPrim + for (auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:fallback", UsdPrimvarReader_int, + preader->fallback) + if ((prop.first == kInputsVarname) && !table.count(kInputsVarname)) { + // Support older spec: `token` for varname + TypedAttribute> tok_attr; + auto ret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, tok_attr); + if (ret.code == ParseResult::ResultCode::Success) { + if (!ConvertTokenAttributeToStringAttribute(tok_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname token type to string type."); + } + DCOUT("`token` attribute is converted to `string` attribute."); + continue; + } else if (ret.code == ParseResult::ResultCode::TypeMismatch) { + TypedAttribute> sdata_attr; + auto sdret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, sdata_attr); + if (sdret.code == ParseResult::ResultCode::Success) { + if (!ConvertStringDataAttributeToStringAttribute(sdata_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname StringData type to string type."); + } + DCOUT("StringData attribute is converted to `string` attribute."); + continue; + } else if (sdret.code == ParseResult::ResultCode::TypeMismatch) { + auto sret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, preader->varname); + if (sret.code == ParseResult::ResultCode::Success) { + DCOUT("Parsed string typed inputs:varname."); + // ok + continue; + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {}", sret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {} {}", to_string(sdret.code), sdret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("{} {}", to_string(ret.code), ret.err)); + } + } + PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:result", + UsdPrimvarReader_int, preader->result) + ADD_PROPERTY(table, prop, UsdPrimvarReader_int, preader->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + return false; +} + +template <> +bool ReconstructShader( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + UsdPrimvarReader_float *preader, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) +{ + (void)spec; + (void)references; + (void)options; + std::set table; + table.insert("info:id"); // `info:id` is already parsed in ReconstructPrim + for (auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:fallback", UsdPrimvarReader_float, + preader->fallback) + if ((prop.first == kInputsVarname) && !table.count(kInputsVarname)) { + // Support older spec: `token` for varname + TypedAttribute> tok_attr; + auto ret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, tok_attr); + if (ret.code == ParseResult::ResultCode::Success) { + if (!ConvertTokenAttributeToStringAttribute(tok_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname token type to string type."); + } + DCOUT("`token` attribute is converted to `string` attribute."); + continue; + } else if (ret.code == ParseResult::ResultCode::TypeMismatch) { + TypedAttribute> sdata_attr; + auto sdret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, sdata_attr); + if (sdret.code == ParseResult::ResultCode::Success) { + if (!ConvertStringDataAttributeToStringAttribute(sdata_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname StringData type to string type."); + } + DCOUT("StringData attribute is converted to `string` attribute."); + continue; + } else if (sdret.code == ParseResult::ResultCode::TypeMismatch) { + auto sret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, preader->varname); + if (sret.code == ParseResult::ResultCode::Success) { + DCOUT("Parsed string typed inputs:varname."); + // ok + continue; + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {}", sret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {} {}", to_string(sdret.code), sdret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("{} {}", to_string(ret.code), ret.err)); + } + } + PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:result", + UsdPrimvarReader_float, preader->result) + ADD_PROPERTY(table, prop, UsdPrimvarReader_float, preader->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + return false; +} + +template <> +bool ReconstructShader( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + UsdPrimvarReader_float2 *preader, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) +{ + (void)spec; + (void)references; + (void)options; + std::set table; + table.insert("info:id"); // `info:id` is already parsed in ReconstructPrim + for (auto &prop : properties) { + DCOUT("Primreader_float2 prop = " << prop.first); + if ((prop.first == kInputsVarname) && !table.count(kInputsVarname)) { + // Support older spec: `token` for varname + TypedAttribute> tok_attr; + auto ret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, tok_attr); + if (ret.code == ParseResult::ResultCode::Success) { + if (!ConvertTokenAttributeToStringAttribute(tok_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname token type to string type."); + } + DCOUT("`token` attribute is converted to `string` attribute."); + continue; + } else if (ret.code == ParseResult::ResultCode::TypeMismatch) { + TypedAttribute> sdata_attr; + auto sdret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, sdata_attr); + if (sdret.code == ParseResult::ResultCode::Success) { + if (!ConvertStringDataAttributeToStringAttribute(sdata_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname StringData type to string type."); + } + DCOUT("StringData attribute is converted to `string` attribute."); + continue; + } else if (sdret.code == ParseResult::ResultCode::TypeMismatch) { + auto sret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, preader->varname); + if (sret.code == ParseResult::ResultCode::Success) { + DCOUT("Parsed string typed inputs:varname."); + // ok + continue; + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {}", sret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {} {}", to_string(sdret.code), sdret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("{} {}", to_string(ret.code), ret.err)); + } + } + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:fallback", UsdPrimvarReader_float2, + preader->fallback) + PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:result", + UsdPrimvarReader_float2, preader->result) + ADD_PROPERTY(table, prop, UsdPrimvarReader_float2, preader->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + return true; +} + +template <> +bool ReconstructShader( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + UsdPrimvarReader_float3 *preader, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) +{ + (void)spec; + (void)references; + (void)options; + std::set table; + table.insert("info:id"); // `info:id` is already parsed in ReconstructPrim + for (auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:fallback", UsdPrimvarReader_float3, + preader->fallback) + if ((prop.first == kInputsVarname) && !table.count(kInputsVarname)) { + // Support older spec: `token` for varname + TypedAttribute> tok_attr; + auto ret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, tok_attr); + if (ret.code == ParseResult::ResultCode::Success) { + if (!ConvertTokenAttributeToStringAttribute(tok_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname token type to string type."); + } + DCOUT("`token` attribute is converted to `string` attribute."); + continue; + } else if (ret.code == ParseResult::ResultCode::TypeMismatch) { + TypedAttribute> sdata_attr; + auto sdret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, sdata_attr); + if (sdret.code == ParseResult::ResultCode::Success) { + if (!ConvertStringDataAttributeToStringAttribute(sdata_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname StringData type to string type."); + } + DCOUT("StringData attribute is converted to `string` attribute."); + continue; + } else if (sdret.code == ParseResult::ResultCode::TypeMismatch) { + auto sret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, preader->varname); + if (sret.code == ParseResult::ResultCode::Success) { + DCOUT("Parsed string typed inputs:varname."); + // ok + continue; + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {}", sret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {} {}", to_string(sdret.code), sdret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("{} {}", to_string(ret.code), ret.err)); + } + } + PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:result", + UsdPrimvarReader_float3, preader->result) + ADD_PROPERTY(table, prop, UsdPrimvarReader_float3, preader->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + return true; +} + +template <> +bool ReconstructShader( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + UsdPrimvarReader_float4 *preader, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) +{ + (void)spec; + (void)references; + (void)options; + std::set table; + table.insert("info:id"); // `info:id` is already parsed in ReconstructPrim + + for (auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:fallback", UsdPrimvarReader_float4, + preader->fallback) + if ((prop.first == kInputsVarname) && !table.count(kInputsVarname)) { + // Support older spec: `token` for varname + TypedAttribute> tok_attr; + auto ret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, tok_attr); + if (ret.code == ParseResult::ResultCode::Success) { + if (!ConvertTokenAttributeToStringAttribute(tok_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname token type to string type."); + } + DCOUT("`token` attribute is converted to `string` attribute."); + continue; + } else if (ret.code == ParseResult::ResultCode::TypeMismatch) { + TypedAttribute> sdata_attr; + auto sdret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, sdata_attr); + if (sdret.code == ParseResult::ResultCode::Success) { + if (!ConvertStringDataAttributeToStringAttribute(sdata_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname StringData type to string type."); + } + DCOUT("StringData attribute is converted to `string` attribute."); + continue; + } else if (sdret.code == ParseResult::ResultCode::TypeMismatch) { + auto sret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, preader->varname); + if (sret.code == ParseResult::ResultCode::Success) { + DCOUT("Parsed string typed inputs:varname."); + // ok + continue; + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {}", sret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {} {}", to_string(sdret.code), sdret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("{} {}", to_string(ret.code), ret.err)); + } + } + PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:result", + UsdPrimvarReader_float4, preader->result) + ADD_PROPERTY(table, prop, UsdPrimvarReader_float4, preader->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + return true; +} + +template <> +bool ReconstructShader( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + UsdPrimvarReader_string *preader, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) +{ + (void)spec; + (void)references; + (void)options; + std::set table; + table.insert("info:id"); // `info:id` is already parsed in ReconstructPrim + + for (auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:fallback", UsdPrimvarReader_string, + preader->fallback) + if ((prop.first == kInputsVarname) && !table.count(kInputsVarname)) { + // Support older spec: `token` for varname + TypedAttribute> tok_attr; + auto ret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, tok_attr); + if (ret.code == ParseResult::ResultCode::Success) { + if (!ConvertTokenAttributeToStringAttribute(tok_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname token type to string type."); + } + DCOUT("`token` attribute is converted to `string` attribute."); + continue; + } else if (ret.code == ParseResult::ResultCode::TypeMismatch) { + TypedAttribute> sdata_attr; + auto sdret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, sdata_attr); + if (sdret.code == ParseResult::ResultCode::Success) { + if (!ConvertStringDataAttributeToStringAttribute(sdata_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname StringData type to string type."); + } + DCOUT("StringData attribute is converted to `string` attribute."); + continue; + } else if (sdret.code == ParseResult::ResultCode::TypeMismatch) { + auto sret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, preader->varname); + if (sret.code == ParseResult::ResultCode::Success) { + DCOUT("Parsed string typed inputs:varname."); + // ok + continue; + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {}", sret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {} {}", to_string(sdret.code), sdret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("{} {}", to_string(ret.code), ret.err)); + } + } + PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:result", + UsdPrimvarReader_string, preader->result) + ADD_PROPERTY(table, prop, UsdPrimvarReader_string, preader->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + return true; +} + +template <> +bool ReconstructShader( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + UsdPrimvarReader_vector *preader, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) +{ + (void)spec; + (void)references; + (void)options; + std::set table; + table.insert("info:id"); // `info:id` is already parsed in ReconstructPrim + + for (auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:fallback", UsdPrimvarReader_vector, + preader->fallback) + if ((prop.first == kInputsVarname) && !table.count(kInputsVarname)) { + // Support older spec: `token` for varname + TypedAttribute> tok_attr; + auto ret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, tok_attr); + if (ret.code == ParseResult::ResultCode::Success) { + if (!ConvertTokenAttributeToStringAttribute(tok_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname token type to string type."); + } + DCOUT("`token` attribute is converted to `string` attribute."); + continue; + } else if (ret.code == ParseResult::ResultCode::TypeMismatch) { + TypedAttribute> sdata_attr; + auto sdret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, sdata_attr); + if (sdret.code == ParseResult::ResultCode::Success) { + if (!ConvertStringDataAttributeToStringAttribute(sdata_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname StringData type to string type."); + } + DCOUT("StringData attribute is converted to `string` attribute."); + continue; + } else if (sdret.code == ParseResult::ResultCode::TypeMismatch) { + auto sret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, preader->varname); + if (sret.code == ParseResult::ResultCode::Success) { + DCOUT("Parsed string typed inputs:varname."); + // ok + continue; + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {}", sret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {} {}", to_string(sdret.code), sdret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("{} {}", to_string(ret.code), ret.err)); + } + } + PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:result", + UsdPrimvarReader_vector, preader->result) + ADD_PROPERTY(table, prop, UsdPrimvarReader_vector, preader->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + return true; +} + +template <> +bool ReconstructShader( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + UsdPrimvarReader_normal *preader, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) +{ + (void)spec; + (void)references; + (void)options; + std::set table; + table.insert("info:id"); // `info:id` is already parsed in ReconstructPrim + + for (auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:fallback", UsdPrimvarReader_normal, + preader->fallback) + if ((prop.first == kInputsVarname) && !table.count(kInputsVarname)) { + // Support older spec: `token` for varname + TypedAttribute> tok_attr; + auto ret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, tok_attr); + if (ret.code == ParseResult::ResultCode::Success) { + if (!ConvertTokenAttributeToStringAttribute(tok_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname token type to string type."); + } + DCOUT("`token` attribute is converted to `string` attribute."); + continue; + } else if (ret.code == ParseResult::ResultCode::TypeMismatch) { + TypedAttribute> sdata_attr; + auto sdret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, sdata_attr); + if (sdret.code == ParseResult::ResultCode::Success) { + if (!ConvertStringDataAttributeToStringAttribute(sdata_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname StringData type to string type."); + } + DCOUT("StringData attribute is converted to `string` attribute."); + continue; + } else if (sdret.code == ParseResult::ResultCode::TypeMismatch) { + auto sret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, preader->varname); + if (sret.code == ParseResult::ResultCode::Success) { + DCOUT("Parsed string typed inputs:varname."); + // ok + continue; + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {}", sret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {} {}", to_string(sdret.code), sdret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("{} {}", to_string(ret.code), ret.err)); + } + } + PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:result", + UsdPrimvarReader_normal, preader->result) + ADD_PROPERTY(table, prop, UsdPrimvarReader_normal, preader->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + return true; +} + +template <> +bool ReconstructShader( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + UsdPrimvarReader_point *preader, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) +{ + (void)spec; + (void)references; + (void)options; + std::set table; + table.insert("info:id"); // `info:id` is already parsed in ReconstructPrim + + for (auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:fallback", UsdPrimvarReader_point, + preader->fallback) + if ((prop.first == kInputsVarname) && !table.count(kInputsVarname)) { + // Support older spec: `token` for varname + TypedAttribute> tok_attr; + auto ret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, tok_attr); + if (ret.code == ParseResult::ResultCode::Success) { + if (!ConvertTokenAttributeToStringAttribute(tok_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname token type to string type."); + } + DCOUT("`token` attribute is converted to `string` attribute."); + continue; + } else if (ret.code == ParseResult::ResultCode::TypeMismatch) { + TypedAttribute> sdata_attr; + auto sdret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, sdata_attr); + if (sdret.code == ParseResult::ResultCode::Success) { + if (!ConvertStringDataAttributeToStringAttribute(sdata_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname StringData type to string type."); + } + DCOUT("StringData attribute is converted to `string` attribute."); + continue; + } else if (sdret.code == ParseResult::ResultCode::TypeMismatch) { + auto sret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, preader->varname); + if (sret.code == ParseResult::ResultCode::Success) { + DCOUT("Parsed string typed inputs:varname."); + // ok + continue; + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {}", sret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {} {}", to_string(sdret.code), sdret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("{} {}", to_string(ret.code), ret.err)); + } + } + PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:result", + UsdPrimvarReader_point, preader->result) + ADD_PROPERTY(table, prop, UsdPrimvarReader_point, preader->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + return true; +} + +template <> +bool ReconstructShader( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + UsdPrimvarReader_matrix *preader, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) +{ + (void)spec; + (void)references; + (void)options; + std::set table; + table.insert("info:id"); // `info:id` is already parsed in ReconstructPrim + + for (auto &prop : properties) { + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:fallback", UsdPrimvarReader_matrix, + preader->fallback) + if ((prop.first == kInputsVarname) && !table.count(kInputsVarname)) { + // Support older spec: `token` for varname + TypedAttribute> tok_attr; + auto ret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, tok_attr); + if (ret.code == ParseResult::ResultCode::Success) { + if (!ConvertTokenAttributeToStringAttribute(tok_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname token type to string type."); + } + DCOUT("`token` attribute is converted to `string` attribute."); + continue; + } else if (ret.code == ParseResult::ResultCode::TypeMismatch) { + TypedAttribute> sdata_attr; + auto sdret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, sdata_attr); + if (sdret.code == ParseResult::ResultCode::Success) { + if (!ConvertStringDataAttributeToStringAttribute(sdata_attr, preader->varname)) { + PUSH_ERROR_AND_RETURN("Failed to convert inputs:varname StringData type to string type."); + } + DCOUT("StringData attribute is converted to `string` attribute."); + continue; + } else if (sdret.code == ParseResult::ResultCode::TypeMismatch) { + auto sret = ParseTypedAttribute(table, prop.first, prop.second, kInputsVarname, preader->varname); + if (sret.code == ParseResult::ResultCode::Success) { + DCOUT("Parsed string typed inputs:varname."); + // ok + continue; + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {}", sret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Faied to parse inputs:varname: {} {}", to_string(sdret.code), sdret.err)); + } + } else { + PUSH_ERROR_AND_RETURN(fmt::format("{} {}", to_string(ret.code), ret.err)); + } + } + PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:result", + UsdPrimvarReader_matrix, preader->result) + ADD_PROPERTY(table, prop, UsdPrimvarReader_matrix, preader->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + return true; +} + +template <> +bool ReconstructShader( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + UsdTransform2d *transform, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) +{ + (void)spec; + (void)references; + (void)options; + std::set table; + table.insert("info:id"); // `info:id` is already parsed in ReconstructPrim + for (auto &prop : properties) { + DCOUT("prop = " << prop.first); + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:in", UsdTransform2d, + transform->in) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:rotation", UsdTransform2d, + transform->rotation) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:scale", UsdTransform2d, + transform->scale) + PARSE_TYPED_ATTRIBUTE(table, prop, "inputs:translation", UsdTransform2d, + transform->translation) + PARSE_SHADER_TERMINAL_ATTRIBUTE(table, prop, "outputs:result", + UsdTransform2d, transform->result) + ADD_PROPERTY(table, prop, UsdPrimvarReader_float2, transform->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + Shader *shader, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) +{ + (void)spec; + (void)properties; + (void)options; + + bool is_generic_shader{false}; + auto info_id_prop = properties.find("info:id"); + if (info_id_prop == properties.end()) { + // Guess MatrialX shader. info:id will be resolved by importing referenced .mtlx. + // Treat generic Shader at the moment. + is_generic_shader = true; + //PUSH_ERROR_AND_RETURN("`Shader` must contain `info:id` property."); + } + + std::string shader_type; + if (!is_generic_shader) { + if (info_id_prop->second.is_attribute()) { + const Attribute &attr = info_id_prop->second.get_attribute(); + if ((attr.type_name() == value::kToken)) { + if (auto pv = attr.get_value()) { + shader_type = pv.value().str(); + } else { + PUSH_ERROR_AND_RETURN("Internal errror. `info:id` has invalid type."); + } + } else { + PUSH_ERROR_AND_RETURN("`info:id` attribute must be `token` type."); + } + + // For some corrupted? USDZ file does not have `uniform` variability. + if (attr.variability() != Variability::Uniform) { + PUSH_WARN("`info:id` attribute must have `uniform` variability."); + } + } else { + PUSH_ERROR_AND_RETURN("Invalid type or value for `info:id` property in `Shader`."); + } + + DCOUT("info:id = " << shader_type); + } + + + if (shader_type.compare(kUsdPreviewSurface) == 0) { + UsdPreviewSurface surface; + if (!ReconstructShader(spec, properties, references, + &surface, warn, err, options)) { + PUSH_ERROR_AND_RETURN("Failed to Reconstruct " << kUsdPreviewSurface); + } + shader->info_id = kUsdPreviewSurface; + shader->value = surface; + DCOUT("info_id = " << shader->info_id); + } else if (shader_type.compare(kUsdUVTexture) == 0) { + UsdUVTexture texture; + if (!ReconstructShader(spec, properties, references, + &texture, warn, err, options)) { + PUSH_ERROR_AND_RETURN("Failed to Reconstruct " << kUsdUVTexture); + } + shader->info_id = kUsdUVTexture; + shader->value = texture; + } else if (shader_type.compare(kUsdPrimvarReader_int) == 0) { + UsdPrimvarReader_int preader; + if (!ReconstructShader(spec, properties, references, + &preader, warn, err, options)) { + PUSH_ERROR_AND_RETURN("Failed to Reconstruct " + << kUsdPrimvarReader_int); + } + shader->info_id = kUsdPrimvarReader_int; + shader->value = preader; + } else if (shader_type.compare(kUsdPrimvarReader_float) == 0) { + UsdPrimvarReader_float preader; + if (!ReconstructShader(spec, properties, references, + &preader, warn, err, options)) { + PUSH_ERROR_AND_RETURN("Failed to Reconstruct " + << kUsdPrimvarReader_float); + } + shader->info_id = kUsdPrimvarReader_float; + shader->value = preader; + } else if (shader_type.compare(kUsdPrimvarReader_float2) == 0) { + UsdPrimvarReader_float2 preader; + if (!ReconstructShader(spec, properties, references, + &preader, warn, err, options)) { + PUSH_ERROR_AND_RETURN("Failed to Reconstruct " + << kUsdPrimvarReader_float2); + } + shader->info_id = kUsdPrimvarReader_float2; + shader->value = preader; + } else if (shader_type.compare(kUsdPrimvarReader_float3) == 0) { + UsdPrimvarReader_float3 preader; + if (!ReconstructShader(spec,properties, references, + &preader, warn, err, options)) { + PUSH_ERROR_AND_RETURN("Failed to Reconstruct " + << kUsdPrimvarReader_float3); + } + shader->info_id = kUsdPrimvarReader_float3; + shader->value = preader; + } else if (shader_type.compare(kUsdPrimvarReader_float4) == 0) { + UsdPrimvarReader_float4 preader; + if (!ReconstructShader(spec,properties, references, + &preader, warn, err, options)) { + PUSH_ERROR_AND_RETURN("Failed to Reconstruct " + << kUsdPrimvarReader_float4); + } + shader->info_id = kUsdPrimvarReader_float4; + shader->value = preader; + } else if (shader_type.compare(kUsdPrimvarReader_string) == 0) { + UsdPrimvarReader_string preader; + if (!ReconstructShader(spec,properties, references, + &preader, warn, err, options)) { + PUSH_ERROR_AND_RETURN("Failed to Reconstruct " + << kUsdPrimvarReader_string); + } + shader->info_id = kUsdPrimvarReader_string; + shader->value = preader; + } else if (shader_type.compare(kUsdPrimvarReader_vector) == 0) { + UsdPrimvarReader_vector preader; + if (!ReconstructShader(spec,properties, references, + &preader, warn, err, options)) { + PUSH_ERROR_AND_RETURN("Failed to Reconstruct " + << kUsdPrimvarReader_vector); + } + shader->info_id = kUsdPrimvarReader_vector; + shader->value = preader; + } else if (shader_type.compare(kUsdPrimvarReader_normal) == 0) { + UsdPrimvarReader_normal preader; + if (!ReconstructShader(spec,properties, references, + &preader, warn, err, options)) { + PUSH_ERROR_AND_RETURN("Failed to Reconstruct " + << kUsdPrimvarReader_normal); + } + shader->info_id = kUsdPrimvarReader_normal; + shader->value = preader; + } else if (shader_type.compare(kUsdPrimvarReader_point) == 0) { + UsdPrimvarReader_point preader; + if (!ReconstructShader(spec,properties, references, + &preader, warn, err, options)) { + PUSH_ERROR_AND_RETURN("Failed to Reconstruct " + << kUsdPrimvarReader_point); + } + shader->info_id = kUsdPrimvarReader_point; + shader->value = preader; + } else if (shader_type.compare(kUsdTransform2d) == 0) { + UsdTransform2d transform; + if (!ReconstructShader(spec,properties, references, + &transform, warn, err, options)) { + PUSH_ERROR_AND_RETURN("Failed to Reconstruct " + << kUsdTransform2d); + } + shader->info_id = kUsdTransform2d; + shader->value = transform; + } else { + // Reconstruct as generic ShaderNode + ShaderNode surface; + if (!ReconstructShader(spec,properties, references, + &surface, warn, err, options)) { + PUSH_ERROR_AND_RETURN("Failed to Reconstruct " << shader_type); + } + if (shader_type.size()) { + shader->info_id = shader_type; + } + shader->value = surface; + } + + DCOUT("Shader reconstructed."); + + return true; +} + +template <> +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + Material *material, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options) +{ + (void)spec; + (void)references; + (void)options; + std::set table; + + // TODO: special treatment for properties with 'inputs' and 'outputs' namespace. + + // For `Material`, `outputs` are terminal attribute and treated as input attribute with connection(Should be "token output:surface.connect = "). + for (auto &prop : properties) { + PARSE_SHADER_INPUT_CONNECTION_PROPERTY(table, prop, "outputs:surface", + Material, material->surface) + PARSE_SHADER_INPUT_CONNECTION_PROPERTY(table, prop, "outputs:displacement", + Material, material->displacement) + PARSE_SHADER_INPUT_CONNECTION_PROPERTY(table, prop, "outputs:volume", + Material, material->volume) + PARSE_UNIFORM_ENUM_PROPERTY(table, prop, kPurpose, Purpose, PurposeEnumHandler, Material, + material->purpose, options.strict_allowedToken_check) + ADD_PROPERTY(table, prop, Material, material->props) + PARSE_PROPERTY_END_MAKE_WARN(table, prop) + } + return true; +} + +/// +/// -- PrimSpec +/// + +#define RECONSTRUCT_PRIM_PRIMSPEC_IMPL(__prim_ty) \ +template <> \ +bool ReconstructPrim<__prim_ty>( \ + const PrimSpec &primspec, \ + __prim_ty *prim, \ + std::string *warn, \ + std::string *err, \ + const PrimReconstructOptions &options) { \ + \ + ReferenceList references; /* dummy */ \ + \ + return ReconstructPrim<__prim_ty>(primspec.specifier(), primspec.props(), references, prim, warn, err, options); \ +} + +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(Xform) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(Model) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(Scope) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(GeomMesh) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(GeomPoints) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(GeomCylinder) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(GeomCube) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(GeomCone) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(GeomSphere) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(GeomCapsule) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(GeomBasisCurves) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(GeomCamera) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(GeomSubset) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(SphereLight) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(DomeLight) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(CylinderLight) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(DiskLight) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(DistantLight) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(SkelRoot) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(Skeleton) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(SkelAnimation) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(BlendShape) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(Shader) +RECONSTRUCT_PRIM_PRIMSPEC_IMPL(Material) + + +} // namespace prim + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/prim-reconstruct.hh b/contrib/tinyusdz/tinyusdz_repo/src/prim-reconstruct.hh new file mode 100644 index 000000000..de2194645 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/prim-reconstruct.hh @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2020-2023 Syoyo Fujita. +// Copyright 2023-Present Light Transport Entertainment Inc. +// +// Common Prim reconstruction modules both for USDA and USDC. +// +#pragma once + +#include +#include +#include +#include "prim-types.hh" + +namespace tinyusdz { +namespace prim { + +struct PrimReconstructOptions +{ + bool strict_allowedToken_check{false}; +}; + + +/// +/// Reconstruct property with `xformOp:***` namespace in `properties` to `XformOp` class. +/// Corresponding property are looked up from names in `xformOpOrder`(`token[]`) property. +/// Name of processed xformOp properties are added to `table` +/// TODO: Move to prim-reconstruct.cc? +/// +bool ReconstructXformOpsFromProperties( + const Specifier &spec, + std::set &table, /* inout */ + const PropertyMap &properties, + std::vector *xformOps, + std::string *err); + +/// +/// Reconstruct concrete Prim(e.g. Xform, GeomMesh) from `properties`. +/// +template +bool ReconstructPrim( + const Specifier &spec, + const PropertyMap &properties, + const ReferenceList &references, + T *out, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options = PrimReconstructOptions()); + +/// +/// Reconstruct concrete Prim(e.g. Xform, GeomMesh) from PrimSpec. +/// +template +bool ReconstructPrim( + const PrimSpec &primspec, + T *out, + std::string *warn, + std::string *err, + const PrimReconstructOptions &options = PrimReconstructOptions()); + + +} // namespace prim +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/prim-type-macros.inc b/contrib/tinyusdz/tinyusdz_repo/src/prim-type-macros.inc new file mode 100644 index 000000000..cf157a889 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/prim-type-macros.inc @@ -0,0 +1,31 @@ +// Apply __FUNC to Prim type(class). + +#define APPLY_FUNC_TO_PRIM_TYPES(__FUNC) \ + __FUNC(Model) \ + __FUNC(Scope) \ + __FUNC(Xform) \ + __FUNC(GeomMesh) \ + __FUNC(GeomPoints) \ + __FUNC(GeomCylinder) \ + __FUNC(GeomCube) \ + __FUNC(GeomCone) \ + __FUNC(GeomSphere) \ + __FUNC(GeomCapsule) \ + __FUNC(GeomBasisCurves) \ + __FUNC(GeomNurbsCurves) \ + __FUNC(GeomCamera) \ + __FUNC(PointInstancer) \ + __FUNC(GeomSubset) \ + __FUNC(SphereLight) \ + __FUNC(DomeLight) \ + __FUNC(CylinderLight) \ + __FUNC(DiskLight) \ + __FUNC(DistantLight) \ + __FUNC(RectLight) \ + __FUNC(PortalLight) \ + __FUNC(SkelRoot) \ + __FUNC(Skeleton) \ + __FUNC(SkelAnimation) \ + __FUNC(BlendShape) \ + __FUNC(Shader) \ + __FUNC(Material) diff --git a/contrib/tinyusdz/tinyusdz_repo/src/prim-types.cc b/contrib/tinyusdz/tinyusdz_repo/src/prim-types.cc new file mode 100644 index 000000000..7cf42259b --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/prim-types.cc @@ -0,0 +1,2249 @@ +// SPDX-License-Identifier: MIT +// Copyright 2021 - Present, Syoyo Fujita. +#include +#include +#include +#include +// +#include "prim-types.hh" +#include "str-util.hh" +#include "tiny-format.hh" +// +#include "usdGeom.hh" +#include "usdLux.hh" +#include "usdShade.hh" +#include "usdSkel.hh" +// +#include "common-macros.inc" +#include "pprinter.hh" +#include "value-pprint.hh" + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +//#include "external/pystring.h" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#define PushError(msg) \ + do { \ + if (err) { \ + (*err) += msg; \ + } \ + } while (0) + +namespace tinyusdz { + +nonstd::optional InterpolationFromString(const std::string &v) { + if ("faceVarying" == v) { + return Interpolation::FaceVarying; + } else if ("constant" == v) { + return Interpolation::Constant; + } else if ("uniform" == v) { + return Interpolation::Uniform; + } else if ("vertex" == v) { + return Interpolation::Vertex; + } else if ("varying" == v) { + return Interpolation::Varying; + } + return nonstd::nullopt; +} + +nonstd::optional OrientationFromString(const std::string &v) { + if ("rightHanded" == v) { + return Orientation::RightHanded; + } else if ("leftHanded" == v) { + return Orientation::LeftHanded; + } + return nonstd::nullopt; +} + +bool operator==(const Path &lhs, const Path &rhs) { + if (!lhs.is_valid()) { + return false; + } + + if (!rhs.is_valid()) { + return false; + } + + // Currently simply compare string. + // FIXME: Better Path identity check. + return (lhs.full_path_name() == rhs.full_path_name()); +} + +bool ConvertTokenAttributeToStringAttribute( + const TypedAttribute> &inp, + TypedAttribute> &out) { + + out.metas() = inp.metas(); + + if (inp.is_blocked()) { + out.set_blocked(true); + } else if (inp.is_value_empty()) { + out.set_value_empty(); + } else if (inp.is_connection()) { + out.set_connections(inp.get_connections()); + } else { + Animatable toks; + Animatable strs; + if (inp.get_value(&toks)) { + if (toks.is_scalar()) { + value::token tok; + toks.get_scalar(&tok); + strs.set(tok.str()); + } else if (toks.is_timesamples()) { + auto tok_ts = toks.get_timesamples(); + + for (auto &item : tok_ts.get_samples()) { + strs.add_sample(item.t, item.value.str()); + } + } else if (toks.is_blocked()) { + // TODO + return false; + } + } + out.set_value(strs); + } + + return true; + } + + + +// +// -- Path +// + +Path::Path(const std::string &p, const std::string &prop) { + // + // For absolute path, starts with '/' and no other '/' exists. + // For property part, '.' exists only once. + // + + if (p.empty() && prop.empty()) { + _valid = false; + return; + } + + auto slash_fun = [](const char c) { return c == '/'; }; + auto dot_fun = [](const char c) { return c == '.'; }; + + std::vector prims = split(p, "/"); + + // TODO: More checks('{', '[', ...) + + if (prop.size()) { + // prop should not contain slashes + auto nslashes = std::count_if(prop.begin(), prop.end(), slash_fun); + if (nslashes) { + _valid = false; + return; + } + + // prop does not start with '.' + if (startsWith(prop, ".")) { + _valid = false; + return; + } + } + + if (p[0] == '/') { + // absolute path + + auto ndots = std::count_if(p.begin(), p.end(), dot_fun); + + if (ndots == 0) { + // absolute prim. + _prim_part = p; + + if (prop.size()) { + _prop_part = prop; + _element = prop; + } else { + if (prims.size()) { + _element = prims[prims.size() - 1]; + } else { + _element = p; + } + } + _valid = true; + } else if (ndots == 1) { + // prim_part contains property name. + if (prop.size()) { + // prop must be empty. + _valid = false; + return; + } + + if (p.size() < 3) { + // "/." + _valid = false; + return; + } + + auto loc = p.find_first_of('.'); + if (loc == std::string::npos) { + // ? + _valid = false; + return; + } + + if (loc <= 0) { + // this should not happen though. + _valid = false; + } + + // split + std::string prop_name = p.substr(size_t(loc)); + + _prop_part = prop_name.erase(0, 1); // remove '.' + _prim_part = p.substr(0, size_t(loc)); + _element = _prop_part; // elementName is property path + + _valid = true; + + } else { + _valid = false; + return; + } + + } else if (p[0] == '.') { + // maybe relative(e.g. "./xform", "../xform") + // FIXME: Support relative path fully + +#if 0 + auto nslashes = std::count_if(p.begin(), p.end(), slash_fun); + if (nslashes > 0) { + _valid = false; + return; + } + + _prop_part = p; + _prop_part = _prop_part.erase(0, 1); + _valid = true; +#else + _prim_part = p; + if (prop.size()) { + _prop_part = prop; + _element = prop; + } else { + if (prims.size()) { + _element = prims[prims.size() - 1]; + } else { + _element = p; + } + } + _valid = true; + +#endif + + } else { + // prim.prop + + auto ndots = std::count_if(p.begin(), p.end(), dot_fun); + if (ndots == 0) { + // relative prim. + _prim_part = p; + if (prop.size()) { + _prop_part = prop; + } + _valid = true; + } else if (ndots == 1) { + if (p.size() < 3) { + // "/." + _valid = false; + return; + } + + auto loc = p.find_first_of('.'); + if (loc == std::string::npos) { + // ? + _valid = false; + return; + } + + if (loc <= 0) { + // this should not happen though. + _valid = false; + } + + // split + std::string prop_name = p.substr(size_t(loc)); + + // Check if No '/' in prop_part + if (std::count_if(prop_name.begin(), prop_name.end(), slash_fun) > 0) { + _valid = false; + return; + } + + _prim_part = p.substr(0, size_t(loc)); + _prop_part = prop_name.erase(0, 1); // remove '.' + + _valid = true; + + } else { + _valid = false; + return; + } + } +} + +Path Path::append_property(const std::string &elem) { + Path &p = (*this); + + if (elem.empty()) { + p._valid = false; + return p; + } + + if (is_variantElementName(elem)) { + // variant chars are not supported yet. + p._valid = false; + return p; + } + + if (elem[0] == '[') { + // relational attrib are not supported + p._valid = false; + return p; + } else if (elem[0] == '.') { + // Relative + // std::cerr << "???. elem[0] is '.'\n"; + // For a while, make this valid. + p._valid = false; + return p; + } else { + // TODO: Validate property path. + p._prop_part = elem; + p._element = elem; + + return p; + } +} + +const Path Path::AppendPrim(const std::string &elem) const { + Path p = (*this); // copies + + p.append_prim(elem); + + return p; +} + +const Path Path::AppendElement(const std::string &elem) const { + Path p = (*this); // copies + + p.append_element(elem); + + return p; +} + +const Path Path::AppendProperty(const std::string &elem) const { + Path p = (*this); // copies + + p.append_property(elem); + + return p; +} + +// TODO: Do test more. +// Current implementation may not behave as in pxrUSD's SdfPath's +// _LessThanInternal implementation +bool Path::LessThan(const Path &lhs, const Path &rhs) { + // DCOUT("LessThan"); + if (lhs.is_valid() && rhs.is_valid()) { + // ok + } else { + // Even though this should not happen, + // valid paths is less than invalid paths + return lhs.is_valid(); + } + + // TODO: handle relative path correctly. + if (lhs.is_absolute_path() && rhs.is_absolute_path()) { + // ok + } else { + // Absolute paths are less than relative paths + return lhs.is_absolute_path(); + } + + if (lhs.prim_part() == rhs.prim_part()) { + // compare property + const std::string &lhs_prop_part = lhs.prop_part(); + const std::string &rhs_prop_part = rhs.prop_part(); + + if (lhs_prop_part.empty() || rhs_prop_part.empty()) { + return lhs_prop_part.empty(); + } + + return std::lexicographical_compare( + lhs_prop_part.begin(), lhs_prop_part.end(), rhs_prop_part.begin(), + rhs_prop_part.end()); + + } else { + const std::vector lhs_prim_names = split(lhs.prim_part(), "/"); + const std::vector rhs_prim_names = split(rhs.prim_part(), "/"); + // DCOUT("lhs_names = " << to_string(lhs_prim_names)); + // DCOUT("rhs_names = " << to_string(rhs_prim_names)); + + if (lhs_prim_names.empty() || rhs_prim_names.empty()) { + return lhs_prim_names.empty() && rhs_prim_names.size(); + } + + // common shortest depth. + size_t didx = (std::min)(lhs_prim_names.size(), rhs_prim_names.size()); + + bool same_until_common_depth = true; + for (size_t i = 0; i < didx; i++) { + if (lhs_prim_names[i] != rhs_prim_names[i]) { + same_until_common_depth = false; + break; + } + } + + if (same_until_common_depth) { + // tail differs. compare by depth count. + return lhs_prim_names.size() < rhs_prim_names.size(); + } + + // Walk until common ancestor is found + size_t child_idx = didx - 1; + // DCOUT("common_depth_idx = " << didx << ", lcount = " << + // lhs_prim_names.size() << ", rcount = " << rhs_prim_names.size()); + if (didx > 1) { + for (size_t parent_idx = didx - 2; parent_idx > 0; parent_idx--) { + // DCOUT("parent_idx = " << parent_idx); + if (lhs_prim_names[parent_idx] != rhs_prim_names[parent_idx]) { + child_idx--; + } + } + } + // DCOUT("child_idx = " << child_idx); + + // compare child node + return std::lexicographical_compare( + lhs_prim_names[child_idx].begin(), lhs_prim_names[child_idx].end(), + rhs_prim_names[child_idx].begin(), rhs_prim_names[child_idx].end()); + } +} + +std::pair Path::split_at_root() const { + if (is_absolute_path()) { + if (is_root_path()) { + return std::make_pair(Path("/", ""), Path()); + } + + std::string p = full_path_name(); + + if (p.size() < 2) { + // Never should reach here. just in case + return std::make_pair(*this, Path()); + } + + // Fine 2nd '/' + auto ret = + std::find_if(p.begin() + 1, p.end(), [](char c) { return c == '/'; }); + + if (ret != p.end()) { + auto ndist = std::distance(p.begin(), ret); // distance from str[0] + if (ndist < 1) { + // This should not happen though. + return std::make_pair(*this, Path()); + } + size_t n = size_t(ndist); + std::string root = p.substr(0, n); + std::string siblings = p.substr(n); + + Path rP(root, ""); + Path sP(siblings, ""); + + return std::make_pair(rP, sP); + } + + return std::make_pair(*this, Path()); + } else { + return std::make_pair(Path(), *this); + } +} + +bool Path::has_prefix(const Path &prefix) const { + if (!is_valid() || !prefix.is_valid()) { + return false; + } + + if (prefix.is_prim_property_path()) { + // No hierarchy in Prim's property path, so use ==. + return full_path_name() == prefix.full_path_name(); + } else if (prefix.is_prim_path()) { + // '/', prefix = '/' + if (is_root_path() && prefix.is_root_path()) { + // DCOUT("both are root path"); + return true; + } + + // example: + // - '/bora', prefix = '/' + // - '/bora/dora', prefix = '/' + if (is_absolute_path() && prefix.is_root_path()) { + // DCOUT("prefix is root path"); + return true; + } + + const std::vector prim_names = split(prim_part(), "/"); + const std::vector prefix_prim_names = + split(prefix.prim_part(), "/"); + // DCOUT("prim_names = " << to_string(prim_names)); + // DCOUT("prefix.prim_names = " << to_string(prefix_prim_names)); + + if (prim_names.empty() || prefix_prim_names.empty()) { + return false; + } + + if (prim_names.size() < prefix_prim_names.size()) { + return false; + } + + size_t depth = prefix_prim_names.size(); + if (depth < 1) { // just in case + return false; + } + + // Move to prefix's path depth and compare each elementName of Prim tree + // towards the root. comapre from tail would find a difference earlier. + while (depth > 0) { + if (prim_names[depth - 1] != prefix_prim_names[depth - 1]) { + return false; + } + depth--; + } + + // DCOUT("has_prefix"); + return true; + + } else { + // TODO: property-only path. + DCOUT("TODO: Unsupported Path type in has_prefix()"); + return false; + } +} + +Path Path::append_element(const std::string &elem) { + Path &p = (*this); + + if (elem.empty()) { + p._valid = false; + return p; + } + + // {variant=value} + if (is_variantElementName(elem)) { + std::array variant; + if (tokenize_variantElement(elem, &variant)) { + _variant_part = variant[0]; + _variant_selection_part = variant[0]; + _prim_part += elem; + _element = elem; + return p; + } else { + p._valid = false; + } + } + + if (elem[0] == '[') { + // relational attrib are not supported + p._valid = false; + return p; + } else if (elem[0] == '.') { + // Relative path + // For a while, make this valid. + p._valid = false; + return p; + } else { + // std::cout << "elem " << elem << "\n"; + if ((p._prim_part.size() == 1) && (p._prim_part[0] == '/')) { + p._prim_part += elem; + } else { + // TODO: Validate element name. + p._prim_part += '/' + elem; + } + + // Also store raw element name + p._element = elem; + + return p; + } +} + +Path Path::get_parent_path() const { + if (!_valid) { + return Path(); + } + + if (is_root_path()) { + Path p("", ""); + return p; + } + + if (is_prim_property_path()) { + // return prim part + return Path(prim_part(), ""); + } + + size_t n = _prim_part.find_last_of('/'); + if (n == std::string::npos) { + // relative path(e.g. "bora") or propery only path(e.g. ".myval"). + return Path(); + } + + if (n == 0) { + // return root + return Path("/", ""); + } + + return Path(_prim_part.substr(0, n), ""); +} + +Path Path::get_parent_prim_path() const { + if (!_valid) { + return Path(); + } + + if (is_root_prim()) { + return *this; + } + + if (is_prim_property_path()) { + // return prim part + return Path(prim_part(), ""); + } + + size_t n = _prim_part.find_last_of('/'); + if (n == std::string::npos) { + // this should never happen though. + return Path(); + } + + if (n == 0) { + // return root + return Path("/", ""); + } + + return Path(_prim_part.substr(0, n), ""); +} + +const std::string &Path::element_name() const { + if (_element.empty()) { + // Get last item. + std::vector tokenized_prim_names = split(prim_part(), "/"); + if (tokenized_prim_names.size()) { + _element = tokenized_prim_names[size_t(tokenized_prim_names.size() - 1)]; + } + } + + return _element; +} + +nonstd::optional KindFromString(const std::string &str) { + if (str == "model") { + return Kind::Model; + } else if (str == "group") { + return Kind::Group; + } else if (str == "assembly") { + return Kind::Assembly; + } else if (str == "component") { + return Kind::Component; + } else if (str == "subcomponent") { + return Kind::Subcomponent; + } else if (str == "sceneLibrary") { + // https://developer.apple.com/documentation/arkit/usdz_schemas_for_ar/scenelibrary + return Kind::SceneLibrary; + } else if (str.empty()) { + return nonstd::nullopt; + } else { + return Kind::UserDef; + } +} + +bool ValidatePrimElementName(const std::string &name) { + if (name.empty()) { + return false; + } + + // alphanum + '_' + // first char must not be number. + + if (std::isdigit(int(name[0]))) { + return false; + } else if (std::isalpha(int(name[0]))) { + // ok + } else if (name[0] == '_') { + // ok + } else { + return false; + } + + for (size_t i = 1; i < name.size(); i++) { + if (std::isalnum(int(name[i])) || (name[i] == '_')) { + // ok + } else { + return false; + } + } + + return true; +} + +// +// -- Prim +// + +namespace { + +const PrimMeta *GetPrimMeta(const value::Value &v) { + // Lookup PrimMeta variable in Prim class + +#define GET_PRIM_META(__ty) \ + if (v.as<__ty>()) { \ + return &(v.as<__ty>()->meta); \ + } + + GET_PRIM_META(Model) + GET_PRIM_META(Scope) + GET_PRIM_META(Xform) + GET_PRIM_META(GPrim) + GET_PRIM_META(GeomMesh) + GET_PRIM_META(GeomPoints) + GET_PRIM_META(GeomCube) + GET_PRIM_META(GeomCapsule) + GET_PRIM_META(GeomCylinder) + GET_PRIM_META(GeomSphere) + GET_PRIM_META(GeomCone) + GET_PRIM_META(GeomSubset) + GET_PRIM_META(GeomCamera) + GET_PRIM_META(GeomBasisCurves) + GET_PRIM_META(DomeLight) + GET_PRIM_META(SphereLight) + GET_PRIM_META(CylinderLight) + GET_PRIM_META(DiskLight) + GET_PRIM_META(RectLight) + GET_PRIM_META(Material) + GET_PRIM_META(Shader) + // GET_PRIM_META(UsdPreviewSurface) + // GET_PRIM_META(UsdUVTexture) + // GET_PRIM_META(UsdPrimvarReader_int) + // GET_PRIM_META(UsdPrimvarReader_float) + // GET_PRIM_META(UsdPrimvarReader_float2) + // GET_PRIM_META(UsdPrimvarReader_float3) + // GET_PRIM_META(UsdPrimvarReader_float4) + GET_PRIM_META(SkelRoot) + GET_PRIM_META(Skeleton) + GET_PRIM_META(SkelAnimation) + GET_PRIM_META(BlendShape) + +#undef GET_PRIM_META + + return nullptr; +} + +PrimMeta *GetPrimMeta(value::Value &v) { + // Lookup PrimMeta variable in Prim class + +#define GET_PRIM_META(__ty) \ + if (v.as<__ty>()) { \ + return &(v.as<__ty>()->meta); \ + } + + GET_PRIM_META(Model) + GET_PRIM_META(Scope) + GET_PRIM_META(Xform) + GET_PRIM_META(GPrim) + GET_PRIM_META(GeomMesh) + GET_PRIM_META(GeomPoints) + GET_PRIM_META(GeomCube) + GET_PRIM_META(GeomCapsule) + GET_PRIM_META(GeomCylinder) + GET_PRIM_META(GeomSphere) + GET_PRIM_META(GeomCone) + GET_PRIM_META(GeomSubset) + GET_PRIM_META(GeomCamera) + GET_PRIM_META(GeomBasisCurves) + GET_PRIM_META(DomeLight) + GET_PRIM_META(SphereLight) + GET_PRIM_META(CylinderLight) + GET_PRIM_META(DiskLight) + GET_PRIM_META(RectLight) + GET_PRIM_META(Material) + GET_PRIM_META(Shader) + // GET_PRIM_META(UsdPreviewSurface) + // GET_PRIM_META(UsdUVTexture) + // GET_PRIM_META(UsdPrimvarReader_int) + // GET_PRIM_META(UsdPrimvarReader_float) + // GET_PRIM_META(UsdPrimvarReader_float2) + // GET_PRIM_META(UsdPrimvarReader_float3) + // GET_PRIM_META(UsdPrimvarReader_float4) + GET_PRIM_META(SkelRoot) + GET_PRIM_META(Skeleton) + GET_PRIM_META(SkelAnimation) + GET_PRIM_META(BlendShape) + +#undef GET_PRIM_META + + return nullptr; +} + +} // namespace + +/// +/// Stage +/// + +// +// TODO: Move to prim-types.cc +// + +nonstd::optional GetPrimElementName(const value::Value &v) { + // Since multiple get_value() call consumes lots of stack size(depends on + // sizeof(T)?), Following code would produce 100KB of stack in debug build. So + // use as() instead(as() => roughly 2000 bytes for stack size). +#if 0 + // + // TODO: Find a better C++ way... use a std::function? + // + if (auto pv = v.get_value()) { + return Path(pv.value().name, ""); + } + if (auto pv = v.get_value()) { + return Path(pv.value().name, ""); + } + if (auto pv = v.get_value()) { + return Path(pv.value().name, ""); + } + if (auto pv = v.get_value()) { + return Path(pv.value().name, ""); + } + if (auto pv = v.get_value()) { + return Path(pv.value().name, ""); + } + if (auto pv = v.get_value()) { + return Path(pv.value().name, ""); + } + if (auto pv = v.get_value()) { + return Path(pv.value().name, ""); + } + if (auto pv = v.get_value()) { + return Path(pv.value().name, ""); + } + if (auto pv = v.get_value()) { + return Path(pv.value().name, ""); + } + if (auto pv = v.get_value()) { + return Path(pv.value().name, ""); + } + if (auto pv = v.get_value()) { + return Path(pv.value().name, ""); + } + if (auto pv = v.get_value()) { + return Path(pv.value().name, ""); + } + if (auto pv = v.get_value()) { + return Path(pv.value().name, ""); + } + + if (auto pv = v.get_value()) { + return Path(pv.value().name, ""); + } + if (auto pv = v.get_value()) { + return Path(pv.value().name, ""); + } + // if (auto pv = v.get_value()) { return + // Path(pv.value().name); } if (auto pv = v.get_value()) { + // return Path(pv.value().name); } + + if (auto pv = v.get_value()) { + return Path(pv.value().name, ""); + } + if (auto pv = v.get_value()) { + return Path(pv.value().name, ""); + } + // if (auto pv = v.get_value()) { return Path(pv.value().name); } + // if (auto pv = v.get_value()) { \ + return v.as<__ty>()->name; \ + } else + + EXTRACT_NAME_AND_RETURN_PATH(Model) + EXTRACT_NAME_AND_RETURN_PATH(Scope) + EXTRACT_NAME_AND_RETURN_PATH(Xform) + EXTRACT_NAME_AND_RETURN_PATH(GPrim) + EXTRACT_NAME_AND_RETURN_PATH(GeomMesh) + EXTRACT_NAME_AND_RETURN_PATH(GeomPoints) + EXTRACT_NAME_AND_RETURN_PATH(GeomCube) + EXTRACT_NAME_AND_RETURN_PATH(GeomCapsule) + EXTRACT_NAME_AND_RETURN_PATH(GeomCylinder) + EXTRACT_NAME_AND_RETURN_PATH(GeomSphere) + EXTRACT_NAME_AND_RETURN_PATH(GeomCone) + EXTRACT_NAME_AND_RETURN_PATH(GeomSubset) + EXTRACT_NAME_AND_RETURN_PATH(GeomCamera) + EXTRACT_NAME_AND_RETURN_PATH(GeomBasisCurves) + EXTRACT_NAME_AND_RETURN_PATH(DomeLight) + EXTRACT_NAME_AND_RETURN_PATH(SphereLight) + EXTRACT_NAME_AND_RETURN_PATH(CylinderLight) + EXTRACT_NAME_AND_RETURN_PATH(DiskLight) + EXTRACT_NAME_AND_RETURN_PATH(RectLight) + EXTRACT_NAME_AND_RETURN_PATH(Material) + EXTRACT_NAME_AND_RETURN_PATH(Shader) + // TODO: extract name must be handled in Shader class + EXTRACT_NAME_AND_RETURN_PATH(UsdPreviewSurface) + EXTRACT_NAME_AND_RETURN_PATH(UsdUVTexture) + EXTRACT_NAME_AND_RETURN_PATH(UsdPrimvarReader_int) + EXTRACT_NAME_AND_RETURN_PATH(UsdPrimvarReader_float) + EXTRACT_NAME_AND_RETURN_PATH(UsdPrimvarReader_float2) + EXTRACT_NAME_AND_RETURN_PATH(UsdPrimvarReader_float3) + EXTRACT_NAME_AND_RETURN_PATH(UsdPrimvarReader_float4) + EXTRACT_NAME_AND_RETURN_PATH(UsdPrimvarReader_string) + EXTRACT_NAME_AND_RETURN_PATH(UsdPrimvarReader_normal) + EXTRACT_NAME_AND_RETURN_PATH(UsdPrimvarReader_vector) + EXTRACT_NAME_AND_RETURN_PATH(UsdPrimvarReader_point) + EXTRACT_NAME_AND_RETURN_PATH(UsdPrimvarReader_matrix) + // + EXTRACT_NAME_AND_RETURN_PATH(SkelRoot) + EXTRACT_NAME_AND_RETURN_PATH(Skeleton) + EXTRACT_NAME_AND_RETURN_PATH(SkelAnimation) + EXTRACT_NAME_AND_RETURN_PATH(BlendShape) { return nonstd::nullopt; } + +#undef EXTRACT_NAME_AND_RETURN_PATH + +#endif +} + +bool SetPrimElementName(value::Value &v, const std::string &elementName) { + // Lookup name field of Prim class + bool ok{false}; + +#define SET_ELEMENT_NAME(__name, __ty) \ + if (v.as<__ty>()) { \ + v.as<__ty>()->name = __name; \ + ok = true; \ + } else + + SET_ELEMENT_NAME(elementName, Model) + SET_ELEMENT_NAME(elementName, Scope) + SET_ELEMENT_NAME(elementName, Xform) + SET_ELEMENT_NAME(elementName, GPrim) + SET_ELEMENT_NAME(elementName, GeomMesh) + SET_ELEMENT_NAME(elementName, GeomPoints) + SET_ELEMENT_NAME(elementName, GeomCube) + SET_ELEMENT_NAME(elementName, GeomCapsule) + SET_ELEMENT_NAME(elementName, GeomCylinder) + SET_ELEMENT_NAME(elementName, GeomSphere) + SET_ELEMENT_NAME(elementName, GeomCone) + SET_ELEMENT_NAME(elementName, GeomSubset) + SET_ELEMENT_NAME(elementName, GeomCamera) + SET_ELEMENT_NAME(elementName, GeomBasisCurves) + SET_ELEMENT_NAME(elementName, DomeLight) + SET_ELEMENT_NAME(elementName, SphereLight) + SET_ELEMENT_NAME(elementName, CylinderLight) + SET_ELEMENT_NAME(elementName, DiskLight) + SET_ELEMENT_NAME(elementName, RectLight) + SET_ELEMENT_NAME(elementName, Material) + SET_ELEMENT_NAME(elementName, Shader) + // TODO: set element name must be handled in Shader class + SET_ELEMENT_NAME(elementName, UsdPreviewSurface) + SET_ELEMENT_NAME(elementName, UsdUVTexture) + SET_ELEMENT_NAME(elementName, UsdPrimvarReader_int) + SET_ELEMENT_NAME(elementName, UsdPrimvarReader_float) + SET_ELEMENT_NAME(elementName, UsdPrimvarReader_float2) + SET_ELEMENT_NAME(elementName, UsdPrimvarReader_float3) + SET_ELEMENT_NAME(elementName, UsdPrimvarReader_float4) + SET_ELEMENT_NAME(elementName, UsdPrimvarReader_string) + SET_ELEMENT_NAME(elementName, UsdPrimvarReader_normal) + SET_ELEMENT_NAME(elementName, UsdPrimvarReader_vector) + SET_ELEMENT_NAME(elementName, UsdPrimvarReader_point) + SET_ELEMENT_NAME(elementName, UsdPrimvarReader_matrix) + // + SET_ELEMENT_NAME(elementName, SkelRoot) + SET_ELEMENT_NAME(elementName, Skeleton) + SET_ELEMENT_NAME(elementName, SkelAnimation) + SET_ELEMENT_NAME(elementName, BlendShape) { return false; } + +#undef SET_ELEMENT_NAME + + return ok; +} + +Prim::Prim(const value::Value &rhs) { + // Check if type is Prim(Model(GPrim), usdShade, usdLux, etc.) + if ((value::TypeId::TYPE_ID_MODEL_BEGIN <= rhs.type_id()) && + (value::TypeId::TYPE_ID_MODEL_END > rhs.type_id())) { + if (auto pv = GetPrimElementName(rhs)) { + _path = Path(pv.value(), /* prop part*/ ""); + _elementPath = Path(pv.value(), /* prop part */ ""); + } + + _data = rhs; + } else { + // TODO: Raise an error if rhs is not an Prim + } +} + +Prim::Prim(value::Value &&rhs) { + // Check if type is Prim(Model(GPrim), usdShade, usdLux, etc.) + if ((value::TypeId::TYPE_ID_MODEL_BEGIN <= rhs.type_id()) && + (value::TypeId::TYPE_ID_MODEL_END > rhs.type_id())) { + _data = std::move(rhs); + + if (auto pv = GetPrimElementName(_data)) { + _path = Path(pv.value(), ""); + _elementPath = Path(pv.value(), ""); + } + + } else { + // TODO: Raise an error if rhs is not an Prim + } +} + +Prim::Prim(const std::string &elementPath, const value::Value &rhs) { + // Check if type is Prim(Model(GPrim), usdShade, usdLux, etc.) + if ((value::TypeId::TYPE_ID_MODEL_BEGIN <= rhs.type_id()) && + (value::TypeId::TYPE_ID_MODEL_END > rhs.type_id())) { + _path = Path(elementPath, /* prop part*/ ""); + _elementPath = Path(elementPath, /* prop part */ ""); + + _data = rhs; + SetPrimElementName(_data, elementPath); + } else { + // TODO: Raise an error if rhs is not an Prim + } +} + +Prim::Prim(const std::string &elementPath, value::Value &&rhs) { + // Check if type is Prim(Model(GPrim), usdShade, usdLux, etc.) + if ((value::TypeId::TYPE_ID_MODEL_BEGIN <= rhs.type_id()) && + (value::TypeId::TYPE_ID_MODEL_END > rhs.type_id())) { + _path = Path(elementPath, /* prop part */ ""); + _elementPath = Path(elementPath, /* prop part */ ""); + + _data = std::move(rhs); + SetPrimElementName(_data, elementPath); + } else { + // TODO: Raise an error if rhs is not an Prim + } +} + +bool Prim::add_child(Prim &&rhs, const bool rename_prim_name, + std::string *err) { +#if defined(TINYUSDZ_ENABLE_THREAD) + // TODO: Only take a lock when dirty. + std::lock_guard lock(_mutex); +#endif + + std::string elementName = rhs.element_name(); + + if (elementName.empty()) { + if (rename_prim_name) { + // assign default name `default` + elementName = "default"; + + if (!SetPrimElementName(rhs.get_data(), elementName)) { + if (err) { + (*err) = fmt::format( + "Internal error. cannot modify Prim's elementName.\n"); + } + return false; + } + rhs.element_path() = Path(elementName, /* prop_part */ ""); + } else { + if (err) { + (*err) = "Prim has empty elementName.\n"; + } + return false; + } + } + + if (_children.size() != _childrenNameSet.size()) { + // Rebuild _childrenNames + _childrenNameSet.clear(); + for (size_t i = 0; i < _children.size(); i++) { + if (_children[i].element_name().empty()) { + if (err) { + (*err) = + "Internal error: Existing child Prim's elementName is empty.\n"; + } + return false; + } + + if (_childrenNameSet.count(_children[i].element_name())) { + if (err) { + (*err) = + "Internal error: _children contains Prim with same " + "elementName.\n"; + } + return false; + } + + _childrenNameSet.insert(_children[i].element_name()); + } + } + + DCOUT("elementName = " << elementName); + + if (_childrenNameSet.count(elementName)) { + if (rename_prim_name) { + std::string unique_name; + if (!makeUniqueName(_childrenNameSet, elementName, &unique_name)) { + if (err) { + (*err) = fmt::format( + "Internal error. cannot assign unique name for `{}`.\n", + elementName); + } + return false; + } + + // Ensure valid Prim name + if (!ValidatePrimElementName(unique_name)) { + if (err) { + (*err) = fmt::format( + "Internally generated Prim name `{}` is invalid as a Prim " + "name.\n", + unique_name); + } + return false; + } + + elementName = unique_name; + + // Need to modify both Prim::data::name and Prim::elementPath + DCOUT("elementName = " << elementName); + if (!SetPrimElementName(rhs.get_data(), elementName)) { + if (err) { + (*err) = fmt::format( + "Internal error. cannot modify Prim's elementName.\n"); + } + return false; + } + rhs.element_path() = Path(elementName, /* prop_part */ ""); + } else { + if (err) { + (*err) = fmt::format( + "Prim name(elementName) {} already exists in children.\n", + rhs.element_name()); + } + return false; + } + } + + DCOUT("rhs.elementName = " << rhs.element_name()); + + _childrenNameSet.insert(elementName); + _children.emplace_back(std::move(rhs)); + _child_dirty = true; + + return true; +} + +bool Prim::replace_child(const std::string &child_prim_name, Prim &&rhs, + std::string *err) { +#if defined(TINYUSDZ_ENABLE_THREAD) + // TODO: Only take a lock when dirty. + std::lock_guard lock(_mutex); +#endif + + if (child_prim_name.empty()) { + if (err) { + (*err) += "child_prim_name is empty.\n"; + } + } + + if (!ValidatePrimElementName(child_prim_name)) { + if (err) { + (*err) += + fmt::format("`{}` is not a valid Prim name.\n", child_prim_name); + } + } + + if (_children.size() != _childrenNameSet.size()) { + // Rebuild _childrenNames + _childrenNameSet.clear(); + for (size_t i = 0; i < _children.size(); i++) { + if (_children[i].element_name().empty()) { + if (err) { + (*err) = + "Internal error: Existing child Prim's elementName is empty.\n"; + } + return false; + } + + if (_childrenNameSet.count(_children[i].element_name())) { + if (err) { + (*err) = + "Internal error: _children contains Prim with same " + "elementName.\n"; + } + return false; + } + + _childrenNameSet.insert(_children[i].element_name()); + } + } + + // Simple linear scan + auto result = std::find_if(_children.begin(), _children.end(), + [child_prim_name](const Prim &p) { + return (p.element_name() == child_prim_name); + }); + + if (result != _children.end()) { + // Need to modify both Prim::data::name and Prim::elementPath + if (!SetPrimElementName(rhs.get_data(), child_prim_name)) { + if (err) { + (*err) = + fmt::format("Internal error. cannot modify Prim's elementName.\n"); + } + return false; + } + rhs.element_path() = Path(child_prim_name, /* prop_part */ ""); + + (*result) = std::move(rhs); // replace + + } else { + // Need to modify both Prim::data::name and Prim::elementPath + if (!SetPrimElementName(rhs.get_data(), child_prim_name)) { + if (err) { + (*err) = + fmt::format("Internal error. cannot modify Prim's elementName.\n"); + } + return false; + } + rhs.element_path() = Path(child_prim_name, /* prop_part */ ""); + + _childrenNameSet.insert(child_prim_name); + _children.emplace_back(std::move(rhs)); // add + } + + _child_dirty = true; + + return true; +} + +const std::vector &Prim::get_child_indices_from_primChildren( + bool force_update, bool *indices_is_valid) const { +#if defined(TINYUSDZ_ENABLE_THREAD) + // TODO: Only take a lock when dirty. + std::lock_guard lock(_mutex); +#endif + + if (!force_update && (_primChildrenIndices.size() == _children.size()) && + !_child_dirty) { + // got cache. + if (indices_is_valid) { + (*indices_is_valid) = _primChildrenIndicesIsValid; + } + return _primChildrenIndices; + } + + if (!force_update) { + _child_dirty = false; + } + + if (metas().primChildren.empty()) { + _primChildrenIndices.resize(_children.size()); + std::iota(_primChildrenIndices.begin(), _primChildrenIndices.end(), 0); + _primChildrenIndicesIsValid = true; + if (indices_is_valid) { + (*indices_is_valid) = _primChildrenIndicesIsValid; + } + return _primChildrenIndices; + } + + std::map m; // name -> children() index map + for (size_t i = 0; i < _children.size(); i++) { + m.emplace(_children[i].element_name(), i); + } + std::set table; // to check uniqueness + + // Use the length of primChildren. + _primChildrenIndices.resize(metas().primChildren.size()); + + bool valid = true; + + for (size_t i = 0; i < _primChildrenIndices.size(); i++) { + std::string tok = metas().primChildren[i].str(); + const auto it = m.find(tok); + if (it != m.end()) { + _primChildrenIndices[i] = int64_t(it->second); + + table.insert(it->second); + } else { + // Prim name not found. + _primChildrenIndices[i] = -1; + valid = false; + } + } + + if (table.size() != _primChildrenIndices.size()) { + // duplicated index exists. + valid = false; + } + + _primChildrenIndicesIsValid = valid; + if (indices_is_valid) { + (*indices_is_valid) = _primChildrenIndicesIsValid; + } + + return _primChildrenIndices; +} + +// +// To deal with clang's -Wexit-time-destructors, dynamically allocate buffer for +// PrimMeta. +// +// NOTE: not thread-safe. +// +class EmptyStaticMeta { + private: + EmptyStaticMeta() = default; + + public: + static PrimMeta &GetEmptyStaticMeta() { + if (!s_meta) { + s_meta = new PrimMeta(); + } + + return *s_meta; + } + + ~EmptyStaticMeta() { + delete s_meta; + s_meta = nullptr; + } + + private: + static PrimMeta *s_meta; +}; + +PrimMeta *EmptyStaticMeta::s_meta = nullptr; + +PrimMeta &Prim::metas() { + PrimMeta *p = GetPrimMeta(_data); + if (p) { + return *p; + } + + // TODO: This should not happen. report an error. + return EmptyStaticMeta::GetEmptyStaticMeta(); +} + +const PrimMeta &Prim::metas() const { + const PrimMeta *p = GetPrimMeta(_data); + if (p) { + return *p; + } + + // TODO: This should not happen. report an error. + return EmptyStaticMeta::GetEmptyStaticMeta(); +} + + +bool SetCustomDataByKey(const std::string &key, const MetaVariable &var, + CustomDataType &custom) { + // split by namespace + std::vector names = split(key, ":"); + DCOUT("names = " << to_string(names)); + + if (names.empty()) { + DCOUT("names is empty"); + return false; + } + + if (names.size() > 1024) { + // too deep + DCOUT("too deep"); + return false; + } + + CustomDataType *curr = &custom; + + for (size_t i = 0; i < names.size(); i++) { + const std::string &elemkey = names[i]; + DCOUT("elemkey = " << elemkey); + + if (i == (names.size() - 1)) { + DCOUT("leaf"); + // leaf + (*curr)[elemkey] = var; + } else { + auto it = curr->find(elemkey); + if (it != curr->end()) { + // must be CustomData type + value::Value &data = it->second.get_raw_value(); + CustomDataType *p = data.as(); + if (p) { + curr = p; + } else { + DCOUT("value is not dictionary"); + return false; + } + } else { + // Add empty dictionary. + CustomDataType customData; + curr->emplace(elemkey, customData); + DCOUT("add dict " << elemkey); + + MetaVariable &child = curr->at(elemkey); + value::Value &data = child.get_raw_value(); + CustomDataType *childp = data.as(); + if (!childp) { + DCOUT("childp is null"); + return false; + } + + DCOUT("child = " << print_customData(*childp, "child", uint32_t(i))); + + // renew curr + curr = childp; + } + } + } + + DCOUT("dict = " << print_customData(custom, "custom", 0)); + + return true; +} + +bool HasCustomDataKey(const CustomDataType &custom, const std::string &key) { + // split by namespace + std::vector names = split(key, ":"); + + DCOUT(print_customData(custom, "customData", 0)); + + if (names.empty()) { + DCOUT("empty"); + return false; + } + + if (names.size() > 1024) { + DCOUT("too deep"); + // too deep + return false; + } + + const CustomDataType *curr = &custom; + + for (size_t i = 0; i < names.size(); i++) { + const std::string &elemkey = names[i]; + DCOUT("elemkey = " << elemkey); + + DCOUT("dict = " << print_customData(*curr, "dict", uint32_t(i))); + + auto it = curr->find(elemkey); + if (it == curr->end()) { + DCOUT("key not found"); + return false; + } + + if (i == (names.size() - 1)) { + // leaf .ok + } else { + // must be CustomData type + const value::Value &data = it->second.get_raw_value(); + const CustomDataType *p = data.as(); + if (p) { + curr = p; + } else { + DCOUT("value is not dictionary type."); + return false; + } + } + } + + return true; +} + +bool GetCustomDataByKey(const CustomDataType &custom, const std::string &key, + MetaVariable *var) { + if (!var) { + return false; + } + + DCOUT(print_customData(custom, "customData", 0)); + + // split by namespace + std::vector names = split(key, ":"); + + if (names.empty()) { + return false; + } + + if (names.size() > 1024) { + // too deep + return false; + } + + const CustomDataType *curr = &custom; + + for (size_t i = 0; i < names.size(); i++) { + const std::string &elemkey = names[i]; + + auto it = curr->find(elemkey); + if (it == curr->end()) { + return false; + } + + if (i == (names.size() - 1)) { + // leaf + (*var) = it->second; + } else { + // must be CustomData type + const value::Value &data = it->second.get_raw_value(); + const CustomDataType *p = data.as(); + if (p) { + curr = p; + } else { + return false; + } + } + } + + return true; +} + +namespace { + +bool OverrideCustomDataRec(uint32_t depth, CustomDataType &dst, + const CustomDataType &src, const bool override_existing) { + if (depth > (1024 * 1024 * 128)) { + // too deep + return false; + } + + for (const auto &item : src) { + if (dst.count(item.first)) { + if (override_existing) { + CustomDataType *dst_dict = + dst.at(item.first).get_raw_value().as(); + + const value::Value &src_data = item.second.get_raw_value(); + const CustomDataType *src_dict = src_data.as(); + + // + // Recursively apply override op both types are dict. + // + if (src_dict && dst_dict) { + // recursively override dict + if (!OverrideCustomDataRec(depth + 1, (*dst_dict), (*src_dict), override_existing)) { + return false; + } + + } else { + dst[item.first] = item.second; + } + } + } else { + // add dict value + dst.emplace(item.first, item.second); + } + } + + return true; +} + +} // namespace + +void OverrideDictionary(CustomDataType &dst, const CustomDataType &src, const bool override_existing) { + OverrideCustomDataRec(0, dst, src, override_existing); +} + +AssetInfo PrimMeta::get_assetInfo(bool *is_authored) const { + AssetInfo ainfo; + + if (is_authored) { + (*is_authored) = authored(); + } + + if (authored()) { + ainfo._fields = meta; + + { + MetaVariable identifier_var; + if (GetCustomDataByKey(meta, "identifier", &identifier_var)) { + std::string identifier; + if (identifier_var.get_value(&identifier)) { + ainfo.identifier = identifier; + ainfo._fields.erase("identifier"); + } + } + } + + { + MetaVariable name_var; + if (GetCustomDataByKey(meta, "name", &name_var)) { + std::string name; + if (name_var.get_value(&name)) { + ainfo.name = name; + ainfo._fields.erase("name"); + } + } + } + + { + MetaVariable payloadDeps_var; + if (GetCustomDataByKey(meta, "payloadAssetDependencies", + &payloadDeps_var)) { + std::vector assets; + if (payloadDeps_var.get_value>(&assets)) { + ainfo.payloadAssetDependencies = assets; + ainfo._fields.erase("payloadAssetDependencies"); + } + } + } + + { + MetaVariable version_var; + if (GetCustomDataByKey(meta, "version", &version_var)) { + std::string version; + if (version_var.get_value(&version)) { + ainfo.version = version; + ainfo._fields.erase("version"); + } + } + } + } + + return ainfo; +} + +const std::string PrimMeta::get_kind() const { + + if (kind.has_value()) { + if (kind.value() == Kind::UserDef) { + return _kind_str; + } else { + return to_string(kind.value()); + } + } + + return ""; +} + +bool IsXformablePrim(const Prim &prim) { + uint32_t tyid = prim.type_id(); + + // GeomSubset is not xformable + + switch (tyid) { + case value::TYPE_ID_GPRIM: { + return true; + } + case value::TYPE_ID_GEOM_XFORM: { + return true; + } + case value::TYPE_ID_GEOM_MESH: { + return true; + } + case value::TYPE_ID_GEOM_BASIS_CURVES: { + return true; + } + case value::TYPE_ID_GEOM_SPHERE: { + return true; + } + case value::TYPE_ID_GEOM_CUBE: { + return true; + } + case value::TYPE_ID_GEOM_CYLINDER: { + return true; + } + case value::TYPE_ID_GEOM_CONE: { + return true; + } + case value::TYPE_ID_GEOM_CAPSULE: { + return true; + } + case value::TYPE_ID_GEOM_POINTS: { + return true; + } + // value::TYPE_ID_GEOM_GEOMSUBSET + case value::TYPE_ID_GEOM_POINT_INSTANCER: { + return true; + } + case value::TYPE_ID_GEOM_CAMERA: { + return true; + } + case value::TYPE_ID_LUX_DOME: { + return true; + } + case value::TYPE_ID_LUX_CYLINDER: { + return true; + } + case value::TYPE_ID_LUX_SPHERE: { + return true; + } + case value::TYPE_ID_LUX_DISK: { + return true; + } + case value::TYPE_ID_LUX_DISTANT: { + return true; + } + case value::TYPE_ID_LUX_RECT: { + return true; + } + case value::TYPE_ID_LUX_GEOMETRY: { + return true; + } + case value::TYPE_ID_LUX_PORTAL: { + return true; + } + case value::TYPE_ID_LUX_PLUGIN: { + return true; + } + case value::TYPE_ID_SKEL_ROOT: { + return true; + } + case value::TYPE_ID_SKELETON: { + return true; + } + default: + return false; + } +} + +bool CastToXformable(const Prim &prim, const Xformable **xformable) { + if (!xformable) { + return false; + } + + // __ty = class derived from Xformable. +#define TRY_CAST(__ty) \ + if (auto pv = prim.as<__ty>()) { \ + (*xformable) = pv; \ + return true; \ + } + + // TODO: Use tydra::ApplyToXformable + TRY_CAST(GPrim) + TRY_CAST(Xform) + TRY_CAST(GeomMesh) + TRY_CAST(GeomBasisCurves) + TRY_CAST(GeomCube) + TRY_CAST(GeomSphere) + TRY_CAST(GeomCylinder) + TRY_CAST(GeomCone) + TRY_CAST(GeomCapsule) + TRY_CAST(GeomPoints) + // TRY_CAST(GeomPointInstancer) + TRY_CAST(GeomCamera) + TRY_CAST(SkelRoot) + TRY_CAST(Skeleton) + TRY_CAST(RectLight) + TRY_CAST(DomeLight) + TRY_CAST(CylinderLight) + TRY_CAST(SphereLight) + TRY_CAST(DiskLight) + TRY_CAST(DistantLight) + TRY_CAST(RectLight) + TRY_CAST(GeometryLight) + TRY_CAST(PortalLight) + TRY_CAST(PluginLight) + TRY_CAST(SkelRoot) + TRY_CAST(Skeleton) + + return false; +} + +value::matrix4d GetLocalTransform(const Prim &prim, bool *resetXformStack, + double t, + value::TimeSampleInterpolationType tinterp) { + if (!IsXformablePrim(prim)) { + if (resetXformStack) { + (*resetXformStack) = false; + } + return value::matrix4d::identity(); + } + + // default false + if (resetXformStack) { + (*resetXformStack) = false; + } + + const Xformable *xformable{nullptr}; + if (CastToXformable(prim, &xformable)) { + if (!xformable) { + return value::matrix4d::identity(); + } + + value::matrix4d m; + bool rxs{false}; + nonstd::expected ret = + xformable->GetLocalMatrix(t, tinterp, &rxs); + if (ret) { + if (resetXformStack) { + (*resetXformStack) = rxs; + } + return ret.value(); + } + } + + return value::matrix4d::identity(); +} + +void PrimMetas::update_from(const PrimMetas &rhs, const bool override_authored) { + if (rhs.active.has_value()) { + if (override_authored || !active.has_value()) { + active = rhs.active; + } + } + + if (rhs.hidden.has_value()) { + if (override_authored || !hidden.has_value()) { + hidden = rhs.hidden; + } + } + + if (rhs.kind.has_value()) { + if (override_authored || !kind.has_value()) { + kind = rhs.kind; + } + } + + if (rhs.instanceable.has_value()) { + if (override_authored || !instanceable.has_value()) { + instanceable = rhs.instanceable; + } + } + + if (rhs.assetInfo) { + if (assetInfo) { + OverrideDictionary(assetInfo.value(), rhs.assetInfo.value(), override_authored); + } else if (override_authored) { + assetInfo = rhs.assetInfo; + } + } + + if (rhs.clips) { + if (clips) { + OverrideDictionary(clips.value(), rhs.clips.value(), override_authored); + } else if (override_authored) { + clips = rhs.clips; + } + } + + if (rhs.customData) { + if (customData) { + OverrideDictionary(customData.value(), rhs.customData.value(), override_authored); + } else if (override_authored) { + customData = rhs.customData; + } + } + + if (rhs.doc) { + if (override_authored || !doc.has_value()) { + doc = rhs.doc; + } + } + + if (rhs.comment) { + if (override_authored || !comment.has_value()) { + comment = rhs.comment; + } + } + + if (rhs.apiSchemas) { + if (override_authored || !apiSchemas.has_value()) { + apiSchemas = rhs.apiSchemas; + } + } + + if (rhs.sdrMetadata) { + if (sdrMetadata) { + OverrideDictionary(sdrMetadata.value(), rhs.sdrMetadata.value(), override_authored); + } else if (override_authored) { + sdrMetadata = rhs.sdrMetadata; + } + } + + if (rhs.sceneName) { + if (override_authored || !sceneName.has_value()) { + sceneName = rhs.sceneName; + } + } + + if (rhs.displayName) { + if (override_authored || !displayName.has_value()) { + displayName = rhs.displayName; + } + } + + if (rhs.references) { + if (override_authored || !references.has_value()) { + references = rhs.references; + } + } + if (rhs.payload) { + if (override_authored || !payload.has_value()) { + payload = rhs.payload; + } + } + if (rhs.inherits) { + if (override_authored || !inherits.has_value()) { + inherits = rhs.inherits; + } + } + if (rhs.variantSets) { + if (override_authored || !variantSets.has_value()) { + variantSets = rhs.variantSets; + } + } + if (rhs.variants) { + if (override_authored || !variants.has_value()) { + variants = rhs.variants; + } + } + if (rhs.specializes) { + if (override_authored || !specializes.has_value()) { + specializes = rhs.specializes; + } + } + + if (rhs.unregisteredMetas.size()) { + for (const auto &item : rhs.unregisteredMetas) { + if (unregisteredMetas.count(item.first)) { + if (override_authored) { + unregisteredMetas[item.first] = item.second; + } + } else { + unregisteredMetas[item.first] = item.second; + } + } + } + + OverrideDictionary(meta, rhs.meta, override_authored); +} + +bool AttrMetas::has_colorSpace() const { + return meta.count("colorSpace"); +} + +value::token AttrMetas::get_colorSpace() const { + if (!has_colorSpace()) { + return value::token(); + } + + const MetaVariable &mv = meta.at("colorSpace"); + value::token tok; + if (mv.get_value(&tok)) { + return tok; + } + + return value::token(); +} + +namespace { + +nonstd::optional GetPrimSpecAtPathRec( + const PrimSpec *parent, const std::string &parent_path, const Path &path, + uint32_t depth) { + if (depth > (1024 * 1024 * 128)) { + // Too deep. + return nonstd::nullopt; + } + + if (!parent) { + return nonstd::nullopt; + } + + std::string abs_path; + { + std::string elementName = parent->name(); + + abs_path = parent_path + "/" + elementName; + + if (abs_path == path.full_path_name()) { + return parent; + } + } + + for (const auto &child : parent->children()) { + if (auto pv = GetPrimSpecAtPathRec(&child, abs_path, path, depth + 1)) { + return pv.value(); + } + } + + // not found + return nonstd::nullopt; +} + +bool HasReferencesRec(uint32_t depth, const PrimSpec &primspec, + const uint32_t max_depth = 1024 * 128) { + if (depth > max_depth) { + // too deep + return false; + } + + if (primspec.metas().references) { + return true; + } + + for (auto &child : primspec.children()) { + if (HasReferencesRec(depth + 1, child, max_depth)) { + return true; + } + } + + return false; +} + +bool HasPayloadRec(uint32_t depth, const PrimSpec &primspec, + const uint32_t max_depth = 1024 * 128) { + if (depth > max_depth) { + // too deep + return false; + } + + if (primspec.metas().payload) { + return true; + } + + for (auto &child : primspec.children()) { + if (HasPayloadRec(depth + 1, child, max_depth)) { + return true; + } + } + + return false; +} + +bool HasVariantRec(uint32_t depth, const PrimSpec &primspec, + const uint32_t max_depth = 1024 * 128) { + if (depth > max_depth) { + // too deep + return false; + } + + // TODO: Also check if PrimSpec::variantSets is empty? + if (primspec.metas().variants && primspec.metas().variantSets) { + return true; + } + + for (auto &child : primspec.children()) { + if (HasVariantRec(depth + 1, child, max_depth)) { + return true; + } + } + + return false; +} + +bool HasInheritsRec(uint32_t depth, const PrimSpec &primspec, + const uint32_t max_depth = 1024 * 128) { + if (depth > max_depth) { + // too deep + return false; + } + + if (primspec.metas().inherits) { + return true; + } + + for (auto &child : primspec.children()) { + if (HasInheritsRec(depth + 1, child, max_depth)) { + return true; + } + } + + return false; +} + +bool HasSpecializesRec(uint32_t depth, const PrimSpec &primspec, + const uint32_t max_depth = 1024 * 128) { + if (depth > max_depth) { + // too deep + return false; + } + + if (primspec.metas().specializes) { + return true; + } + + for (auto &child : primspec.children()) { + if (HasSpecializesRec(depth + 1, child, max_depth)) { + return true; + } + } + + return false; +} + +bool HasOverRec(uint32_t depth, const PrimSpec &primspec, + const uint32_t max_depth = 1024 * 128) { + if (depth > max_depth) { + // too deep + return false; + } + + if (primspec.specifier() == Specifier::Over) { + return true; + } + + for (auto &child : primspec.children()) { + if (HasOverRec(depth + 1, child, max_depth)) { + return true; + } + } + + return false; +} + +} // namespace + +bool Layer::find_primspec_at(const Path &path, const PrimSpec **ps, + std::string *err) const { + if (!ps) { + PUSH_ERROR_AND_RETURN("Invalid PrimSpec dst argument"); + } + + if (!path.is_valid()) { + DCOUT("Invalid path."); + PUSH_ERROR_AND_RETURN("Invalid path"); + } + + if (path.is_relative_path()) { + // TODO + PUSH_ERROR_AND_RETURN(fmt::format("TODO: Relative path: {}", path.full_path_name())); + } + + if (!path.is_absolute_path()) { + PUSH_ERROR_AND_RETURN(fmt::format("Path is not absolute path: {}", path.full_path_name())); + } + +#if defined(TINYUSDZ_ENABLE_THREAD) + // TODO: Only take a lock when dirty. + std::lock_guard lock(_mutex); +#endif + + if (_dirty) { + DCOUT("clear cache."); + // Clear cache. + _primspec_path_cache.clear(); + + _dirty = false; + } else { + // First find from a cache. + auto ret = _primspec_path_cache.find(path.prim_part()); + if (ret != _primspec_path_cache.end()) { + DCOUT("Found cache."); + return ret->second; + } + } + + // Brute-force search. + for (const auto &parent : _prim_specs) { + if (auto pv = GetPrimSpecAtPathRec(&parent.second, /* parent_path */ "", + path, /* depth */ 0)) { + (*ps) = pv.value(); + + // Add to cache. + // Assume pointer address does not change unless dirty state changes. + _primspec_path_cache[path.prim_part()] = pv.value(); + return true; + } + } + + return false; +} + +bool Layer::check_unresolved_references(const uint32_t max_depth) const { + bool ret = false; + + for (const auto &item : _prim_specs) { + if (HasReferencesRec(/* depth */ 0, item.second, max_depth)) { + ret = true; + break; + } + } + + _has_unresolved_references = ret; + return _has_unresolved_references; +} + +bool Layer::check_unresolved_payload(const uint32_t max_depth) const { + bool ret = false; + + for (const auto &item : _prim_specs) { + if (HasPayloadRec(/* depth */ 0, item.second, max_depth)) { + ret = true; + break; + } + } + + _has_unresolved_payload = ret; + return _has_unresolved_payload; +} + +bool Layer::check_unresolved_variant(const uint32_t max_depth) const { + bool ret = false; + + for (const auto &item : _prim_specs) { + if (HasVariantRec(/* depth */ 0, item.second, max_depth)) { + ret = true; + break; + } + } + + _has_unresolved_variant = ret; + return _has_unresolved_variant; +} + +bool Layer::check_unresolved_inherits(const uint32_t max_depth) const { + bool ret = false; + + for (const auto &item : _prim_specs) { + if (HasInheritsRec(/* depth */ 0, item.second, max_depth)) { + ret = true; + break; + } + } + + _has_unresolved_inherits = ret; + return _has_unresolved_inherits; +} + +bool Layer::check_unresolved_specializes(const uint32_t max_depth) const { + bool ret = false; + + for (const auto &item : _prim_specs) { + if (HasSpecializesRec(/* depth */ 0, item.second, max_depth)) { + ret = true; + break; + } + } + + _has_unresolved_specializes = ret; + return _has_unresolved_specializes; +} + +bool Layer::check_over_primspec(const uint32_t max_depth) const { + bool ret = false; + + for (const auto &item : _prim_specs) { + if (HasOverRec(/* depth */ 0, item.second, max_depth)) { + ret = true; + break; + } + } + + _has_over_primspec = ret; + return _has_over_primspec; +} + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/prim-types.hh b/contrib/tinyusdz/tinyusdz_repo/src/prim-types.hh new file mode 100644 index 000000000..3fc3bd139 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/prim-types.hh @@ -0,0 +1,4353 @@ +// SPDX-License-Identifier: Apache 2.0 +#pragma once + +#ifdef _MSC_VER +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(TINYUSDZ_ENABLE_THREAD) +#include +#include +#endif + +// +#include "value-types.hh" + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#include "nonstd/expected.hpp" +#include "nonstd/optional.hpp" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include "handle-allocator.hh" +#include "primvar.hh" +// +#include "value-eval-util.hh" + +namespace tinyusdz { + +// Simple Python-like OrderedDict +template +class ordered_dict { + public: + + bool at(const size_t idx, T *dst) const { + if (idx >= _keys.size()) { + return false; + } + + if (!_m.count(_keys[idx])) { + // This should not happen though. + return false; + } + + (*dst) = _m.at(_keys[idx]); + + return true; + } + + bool at(const size_t idx, const T **dst) const { + if (idx >= _keys.size()) { + return false; + } + + if (!_m.count(_keys[idx])) { + // This should not happen though. + return false; + } + + (*dst) = &(_m.at(_keys[idx])); + + return true; + } + + bool count(const std::string &key) const { + return _m.count(key); + } + + void insert(const std::string &key, const T &value) { + if (_m.count(key)) { + // overwrite existing value + } else { + _keys.push_back(key); + } + + _m[key] = value; + } + + T &get_or_add(const std::string &key) { + if (!_m.count(key)) { + _keys.push_back(key); + } + + return _m[key]; + } + + + void insert(const std::string &key, T &&value) { + if (_m.count(key)) { + // overwrite existing value + } else { + _keys.push_back(key); + } + + _m[key] = std::move(value); + } + + bool erase(const std::string &key) { + + if (!_m.count(key)) { + return false; + } + + // linear search + bool erased = false; + size_t idx = 0; + for (size_t i = 0; i < _keys.size(); i++) { + if (key == _keys[i]) { + idx = i; + erased = true; + } + } + + if (!erased) { + return false; + } + + _keys.erase(_keys.begin() + std::ptrdiff_t(idx)); + _m.erase(key); + + return true; + } + + bool at(const std::string &key, T **dst) { + if (!_m.count(key)) { + // This should not happen though. + return false; + } + + (*dst) = &_m.at(key); + + return true; + } + + + bool at(const std::string &key, const T *dst) const { + if (!_m.count(key)) { + // This should not happen though. + return false; + } + + (*dst) = _m.at(key); + + return true; + } + + bool at(const std::string &key, const T **dst) const { + if (!_m.count(key)) { + // This should not happen though. + return false; + } + + (*dst) = &_m.at(key); + + return true; + } + + const std::vector &keys() const { + return _keys; + } + + size_t size() const { return _m.size(); } + + // No operator[] for safety. + + private: + std::vector _keys; + std::map _m; +}; + +// SpecType enum must be same order with pxrUSD's SdfSpecType(since enum value +// is stored in Crate directly) +enum class SpecType { + Unknown = 0, // must be 0 + Attribute, + Connection, + Expression, + Mapper, + MapperArg, + Prim, + PseudoRoot, + Relationship, + RelationshipTarget, + Variant, + VariantSet, + Invalid, // or NumSpecTypes +}; + +enum class Orientation { + RightHanded, // 0 + LeftHanded, + Invalid +}; + +enum class Visibility { + Inherited, // "inherited" (default) + Invisible, // "invisible" + Invalid +}; + +enum class Purpose { + Default, // 0 + Render, // "render" + Proxy, // "proxy" + Guide, // "guide" +}; + +// +// USDZ extension: sceneLibrary +// https://developer.apple.com/documentation/arkit/usdz_schemas_for_ar/scenelibrary +// + +enum class Kind { + Model, + Group, + Assembly, + Component, + Subcomponent, + SceneLibrary, // USDZ extension + UserDef, // Unknown or user defined Kind + Invalid +}; + +// Attribute interpolation +enum class Interpolation { + Constant, // "constant" + Uniform, // "uniform" + Varying, // "varying" + Vertex, // "vertex" + FaceVarying, // "faceVarying" + Invalid +}; + +// NOTE: Attribute cannot have ListEdit qualifier +enum class ListEditQual { + ResetToExplicit, // "unqualified"(no qualifier) + Append, // "append" + Add, // "add" + Delete, // "delete" + Prepend, // "prepend" + Order, // "order" + Invalid +}; + +enum class Axis { X, Y, Z, Invalid }; + +// metrics(UsdGeomLinearUnits in pxrUSD) +// To avoid linkage error, defined as static constexpr function. +struct Units { + static constexpr double Nanometers = 1e-9; + static constexpr double Micrometers = 1e-6; + static constexpr double Millimeters = 0.001; + static constexpr double Centimeters = 0.01; + static constexpr double Meters = 1.0; + static constexpr double Kilometers = 1000; + static constexpr double LightYears = 9.4607304725808e15; + static constexpr double Inches = 0.0254; + static constexpr double Feet = 0.3048; + static constexpr double Yards = 0.9144; + static constexpr double Miles = 1609.344; +}; + +// For PrimSpec +enum class Specifier { + Def, // 0 + Over, + Class, + Invalid +}; + +enum class Permission { + Public, // 0 + Private, + Invalid +}; + +enum class Variability { + Varying, // 0 + Uniform, + Config, + Invalid +}; + +// Return false when invalid character(e.g. '%') exists in a given string. +// This function only validates `elementName` of a Prim(e.g. "dora", "xform1"). +// If you want to validate a Prim path(e.g. "/root/xform1"), +// Use ValidatePrimPath() in path-util.hh +bool ValidatePrimElementName(const std::string &tok); + +/// +/// Simlar to SdfPath. +/// NOTE: We are doging refactoring of Path class, so the following comment may +/// not be correct. +/// +/// We don't need the performance for USDZ, so use naiive implementation +/// to represent Path. +/// Path is something like Unix path, delimited by `/`, ':' and '.' +/// Square brackets('<', '>' is not included) +/// +/// Root path is represented as prim path "/" and elementPath ""(empty). +/// +/// Example: +/// +/// - `/muda/bora.dora` : prim_part is `/muda/bora`, prop_part is `.dora`. +/// - `bora` : Could be Element(leaf) path or Relative path +/// +/// ':' is a namespce delimiter(example `input:muda`). +/// +/// Limitations: +/// +/// - Relational attribute path(`[` `]`. e.g. `/muda/bora[/ari].dora`) is not +/// supported. +/// - Variant chars('{' '}') is not supported(yet). +/// - Relative path(e.g. '../') is not yet supported(TODO) +/// +/// and have more limitatons. +/// +class Path { + public: + // Similar to SdfPathNode + enum class PathType { + Prim, + PrimProperty, + RelationalAttribute, + MapperArg, + Target, + Mapper, + PrimVariantSelection, + Expression, + Root, + }; + + Path() : _valid(false) {} + + static Path make_root_path() { + Path p = Path("/", ""); + // elementPath is empty for root. + p._element = ""; + p._valid = true; + return p; + } + + // Create Path both from Prim Path and Prop + // If `prim` starts + // "/aaa", "bora" => /aaa.bora + // "/aaa", "" => /aaa (prim only) + // "", "bora" => .bora (property only) + // + // Note: This constructor may fail to extract elementName from given `prim` + // and `prop`. It is highly recommended to use AppendPrim() and AppendProperty + // to. construct Path hierarchy(e.g. `/aaa/xform/geom.points`) so that + // elementName is set correctly. + Path(const std::string &prim, const std::string &prop); + + // : prim_part(prim), valid(true) {} + // Path(const std::string &prim, const std::string &prop) + // : prim_part(prim), prop_part(prop) {} + + Path(const Path &rhs) = default; + + Path &operator=(const Path &rhs) { + this->_valid = rhs._valid; + + this->_prim_part = rhs._prim_part; + this->_prop_part = rhs._prop_part; + this->_element = rhs._element; + + return (*this); + } + + std::string full_path_name() const { + std::string s; + if (!_valid) { + s += "#INVALID#"; + } + + s += _prim_part; + if (_prop_part.empty()) { + return s; + } + + s += "." + _prop_part; + + return s; + } + + const std::string &prim_part() const { return _prim_part; } + const std::string &prop_part() const { return _prop_part; } + + const std::string &variant_part() const { + _variant_part_str = + "{" + _variant_part + "=" + _variant_selection_part + "}"; + return _variant_part_str; + } + + void set_path_type(const PathType ty) { _path_type = ty; } + + bool get_path_type(PathType &ty) { + if (_path_type) { + ty = _path_type.value(); + } + return false; + } + + // IsPropertyPath: PrimProperty or RelationalAttribute + bool is_property_path() const { + if (_path_type) { + if ((_path_type.value() == PathType::PrimProperty || + (_path_type.value() == PathType::RelationalAttribute))) { + return true; + } + } + + // TODO: RelationalAttribute + if (_prim_part.empty()) { + return false; + } + + if (_prop_part.size()) { + return true; + } + + return false; + } + + // Is Prim path? + bool is_prim_path() const { + if (_prop_part.size()) { + return false; + } + + if (_prim_part.size()) { + return true; + } + + return false; + } + + // Is Prim's property path? + // True when both PrimPart and PropPart are not empty. + bool is_prim_property_path() const { + if (_prim_part.empty()) { + return false; + } + if (_prop_part.size()) { + return true; + } + return false; + } + + bool is_valid() const { return _valid; } + + bool is_empty() { + return (_prim_part.empty() && _variant_part.empty() && _prop_part.empty()); + } + + // static Path RelativePath() { return Path("."); } + + // Append property path(change internal state) + Path append_property(const std::string &elem); + + // Append prim or variantSelection path(change internal state) + Path append_element(const std::string &elem); + Path append_prim(const std::string &elem) { + return append_element(elem); + } // for legacy + + // Const version. Does not change internal state. + const Path AppendProperty(const std::string &elem) const; + const Path AppendPrim(const std::string &elem) const; + const Path AppendElement(const std::string &elem) const; + + // Get element name(the last element of Path. i.e. Prim's name, Property's + // name) + const std::string &element_name() const; + + /// + /// Split a path to the root(common ancestor) and its siblings + /// + /// example: + /// + /// - / -> [/, Empty] + /// - /bora -> [/bora, Empty] + /// - /bora/dora -> [/bora, /dora] + /// - /bora/dora/muda -> [/bora, /dora/muda] + /// - bora -> [Empty, bora] + /// - .muda -> [Empty, .muda] + /// + std::pair split_at_root() const; + + /// + /// TODO: Deprecate(use get_parent_path() instead) + /// + /// Get parent Prim path. + /// If the given path is a root Prim path(e.g. "/bora"), same Path is + /// returned. + /// + /// example: + /// + /// - / -> invalid Path + /// - /bora -> /bora + /// - /bora/dora -> /bora + /// - /bora/dora.prop -> /bora/dora + /// - dora/bora -> dora + /// - dora -> invalid Path + /// - .dora -> invalid Path(path is property path) + Path get_parent_prim_path() const; + + /// + /// Get parent Path. + /// If the given path is the root path("/") same Path is returned. + /// + /// example: + /// + /// - / -> invalid Path + /// - /bora -> / + /// - /bora/dora -> /bora + /// - /bora/dora.prop -> /bora/dora + /// - dora/bora -> dora + /// - dora -> invalid Path + /// - .dora -> invalid Path(path is property path) + Path get_parent_path() const; + + /// + /// Check if this Path has same prefix for given Path + /// + /// example. + /// rhs path: /bora/dora + /// + /// /bora/dora/muda -> true + /// /bora/dora2 -> fase + /// + /// If the prefix path contains prop part, compare it with == + /// (assume no hierarchy in property part) + /// + bool has_prefix(const Path &rhs) const; + + /// + /// @returns true if a path is '/' only + /// + bool is_root_path() const { + if (!_valid) { + return false; + } + + if ((_prim_part.size() == 1) && (_prim_part[0] == '/')) { + return true; + } + + return false; + } + + /// + /// @returns true if a path is root prim: e.g. '/bora' + /// + bool is_root_prim() const { + if (!_valid) { + return false; + } + + if (is_root_path()) { + return false; + } + + if ((_prim_part.size() > 1) && (_prim_part[0] == '/')) { + // no other '/' except for the fist one + if (_prim_part.find_last_of('/') == 0) { + return true; + } + } + + return false; + } + + bool is_absolute_path() const { + if (_prim_part.size() && _prim_part[0] == '/') { + return true; + } + + return false; + } + + bool is_relative_path() const { + if (_prim_part.size()) { + return !is_absolute_path(); + } + + return true; // prop part only + } + +#if 0 // TODO: rmove + bool is_variant_selection_path() const { + if (!is_valid()) { + return false; + } + + if (_variant_part.size()) { + return true; + } + + return false; + } +#endif + + // Strip '/' + Path &make_relative() { + if (is_absolute_path() && (_prim_part.size() > 1)) { + // Remove first '/' + _prim_part.erase(0, 1); + } + return *this; + } + + const Path make_relative(Path &&rhs) { + (*this) = std::move(rhs); + + return make_relative(); + } + + static const Path make_relative(const Path &rhs) { + Path p = rhs; // copy + return p.make_relative(); + } + + static bool LessThan(const Path &lhs, const Path &rhs); + + // To sort paths lexicographically. + // TODO: consider abs and relative path correctly + bool operator<(const Path &rhs) const { + if (full_path_name() == rhs.full_path_name()) { + return false; + } + + if (prim_part().empty() || rhs.prim_part().empty()) { + return prim_part().empty() && rhs.prim_part().size(); + } + + return LessThan(*this, rhs); + } + + private: + std::string _prim_part; // e.g. /Model/MyMesh, MySphere + std::string _prop_part; // e.g. visibility (`.` is not included) + std::string _variant_part; // e.g. `variantColor` for {variantColor=green} + std::string _variant_selection_part; // e.g. `green` for {variantColor=green} + // . Could be empty({variantColor=}). + mutable std::string _variant_part_str; // str buffer for variant_part() + mutable std::string _element; // Element name + + nonstd::optional _path_type; // Currently optional. + + bool _valid{false}; +}; + +#if 0 +/// +/// Split Path by the delimiter(e.g. "/") then create lists. +/// +class TokenizedPath { + public: + TokenizedPath() {} + + TokenizedPath(const Path &path) { + std::string s = path.prop_part(); + if (s.empty()) { + // ??? + return; + } + + if (s[0] != '/') { + // Path must start with "/" + return; + } + + s.erase(0, 1); + + char delimiter = '/'; + size_t pos{0}; + while ((pos = s.find(delimiter)) != std::string::npos) { + std::string token = s.substr(0, pos); + _tokens.push_back(token); + s.erase(0, pos + sizeof(char)); + } + + if (!s.empty()) { + // leaf element + _tokens.push_back(s); + } + } + + private: + std::vector _tokens; +}; +#endif + +bool operator==(const Path &lhs, const Path &rhs); + +// variants in Prim Meta. +// +// e.g. +// variants = { +// string variant0 = "bora" +// string variant1 = "dora" +// } +// pxrUSD uses dict type for the content, but TinyUSDZ only accepts list of +// strings for now +// +using VariantSelectionMap = std::map; + +class MetaVariable; + +// TODO: Use `Dictionary` and deprecate CustomDataType +using CustomDataType = std::map; + +using Dictionary = CustomDataType; // alias to CustomDataType + +/// +/// Helper function to access CustomData(dictionary). +/// Recursively process into subdictionaries when a key contains namespaces(':') +/// +bool HasCustomDataKey(const Dictionary &customData, const std::string &key); +bool GetCustomDataByKey(const Dictionary &customData, const std::string &key, + /* out */ MetaVariable *dst); +bool SetCustomDataByKey(const std::string &key, const MetaVariable &val, + /* inout */ Dictionary &customData); + +void OverrideDictionary(Dictionary &customData, const Dictionary &src, const bool override_existing = true); + +// Variable class for Prim and Attribute Metadataum. +// +// - Accepts limited number of types for value +// - No 'custom' keyword +// - 'None'(Value Block) is supported for some type(at least `references` and +// `payload` accepts None) +// - No TimeSamples, No Connection, No Relationship(`rel`) +// - Value must be assigned(e.g. "float myval = 1.3"). So no definition only +// syntax("float myval") +// - Can be string only(no type information) +// - Its variable name is interpreted as "comment" +// +class MetaVariable { + public: + MetaVariable &operator=(const MetaVariable &rhs) { + _name = rhs._name; + _value = rhs._value; + + return *this; + } + + template + MetaVariable(const T &v) { + set_value(v); + } + + MetaVariable(const MetaVariable &rhs) { + _name = rhs._name; + _value = rhs._value; + } + + template + MetaVariable(const std::string &name, const T &v) { + set_value(name, v); + } + + // template + // bool is() const { + // return value.index() == ValueType::index_of(); + // } + + bool is_valid() const { + return _value.type_id() != value::TypeTraits::type_id(); + } + + //// TODO + // bool is_timesamples() const { return false; } + + MetaVariable() = default; + + // + // custom data must have some value, so no set_type() + // OK "float myval = 1" + // NG "float myval" + // + template + void set_value(const T &v) { + // TODO: Check T is supported type for Metadatum. + _value = v; + + _name = std::string(); // empty + } + + template + void set_value(const std::string &name, const T &v) { + // TODO: Check T is supported type for Metadatum. + _value = v; + + _name = name; + } + + template + bool get_value(T *dst) const { + if (!dst) { + return false; + } + + if (const T *v = _value.as()) { + (*dst) = *v; + return true; + } + + return false; + } + + template + nonstd::optional get_value() const { + if (const T *v = _value.as()) { + return *v; + } + + return nonstd::nullopt; + } + + void set_name(const std::string &name) { _name = name; } + const std::string &get_name() const { return _name; } + + const value::Value &get_raw_value() const { return _value; } + value::Value &get_raw_value() { return _value; } + + // No set_type_name() + const std::string type_name() const { return TypeName(*this); } + + uint32_t type_id() const { return TypeId(*this); } + + bool is_blocked() const { + return type_id() == value::TypeId::TYPE_ID_VALUEBLOCK; + } + + private: + static std::string TypeName(const MetaVariable &v) { + return v._value.type_name(); + } + + static uint32_t TypeId(const MetaVariable &v) { return v._value.type_id(); } + + private: + value::Value _value{nullptr}; + std::string _name; +}; + +struct AssetInfo { + // builtin fields + value::AssetPath identifier; + std::string name; + std::vector payloadAssetDependencies; + std::string version; + + // Other fields + Dictionary _fields; +}; + + +// USDZ AR class? +// Preliminary_Trigger, +// Preliminary_PhysicsGravitationalForce, +// Preliminary_InfiniteColliderPlane, +// Preliminary_ReferenceImage, +// Preliminary_Action, +// Preliminary_Text, + +struct APISchemas { + // TinyUSDZ does not allow user-supplied API schema for now + enum class APIName { + MaterialBindingAPI, // "MaterialBindingAPI" + SkelBindingAPI, // "SkelBindingAPI" + ShapingAPI, // "ShapingAPI"(usdLux) + CollectionAPI, // "CollectionAPI" + // USDZ AR extensions + Preliminary_AnchoringAPI, + Preliminary_PhysicsColliderAPI, + Preliminary_PhysicsMaterialAPI, + Preliminary_PhysicsRigidBodyAPI, + }; + + ListEditQual listOpQual{ListEditQual::ResetToExplicit}; // must be 'prepend' + + // std::get<1>: instance name. For Multi-apply API Schema e.g. + // `material:MainMaterial` for `CollectionAPI:material:MainMaterial` + std::vector> names; +}; + +// SdfLayerOffset +struct LayerOffset { + double _offset{0.0}; + double _scale{1.0}; +}; + +// SdfReference +struct Reference { + value::AssetPath asset_path; + Path prim_path; + LayerOffset layerOffset; + Dictionary customData; +}; + +// SdfPayload +struct Payload { + value::AssetPath asset_path; // std::string in SdfPayload + Path prim_path; + LayerOffset layerOffset; // from 0.8.0 + // No customData for Payload + + // NOTE: pxrUSD encodes `payload = None` as Payload with empty paths in USDC(Crate). + // (Not ValueBlock) + bool is_none() const { + return asset_path.GetAssetPath().empty() && !prim_path.is_valid(); + } +}; + +// Metadata for Prim +struct PrimMetas { + nonstd::optional active; // 'active' + nonstd::optional hidden; // 'hidden' + nonstd::optional kind; // 'kind'. user-defined kind value is stored in _kind_str; + std::string _kind_str; + + nonstd::optional + assetInfo; // 'assetInfo' // TODO: Use AssetInfo? + nonstd::optional customData; // `customData` + nonstd::optional doc; // 'documentation' + nonstd::optional + comment; // 'comment' (String only metadata value) + nonstd::optional apiSchemas; // 'apiSchemas' + nonstd::optional + sdrMetadata; // 'sdrMetadata' (usdShade Prim only?) + + nonstd::optional instanceable; // 'instanceable' + nonstd::optional clips; // 'clips' + + // String representation of Kind. + // For user-defined Kind, it returns `_kind_str` + const std::string get_kind() const; + + // + // AssetInfo utility function + // + // Convert CustomDataType to AssetInfo + AssetInfo get_assetInfo(bool *authored = nullptr) const; + + // + // Compositions + // + nonstd::optional>> references; + nonstd::optional>> + payload; // NOTE: not `payloads` + nonstd::optional>> + inherits; // 'inherits' + nonstd::optional>> + variantSets; // 'variantSets'. Could be `token` but treat as + // `string`(Crate format uses `string`) + + nonstd::optional variants; // `variants` + + nonstd::optional>> + specializes; // 'specializes' + + // USDZ extensions + nonstd::optional sceneName; // 'sceneName' + + // Omniverse extensions(TODO: Use UTF8 string type?) + // https://github.com/PixarAnimationStudios/USD/pull/2055 + nonstd::optional displayName; // 'displayName' + + // Unregistered metadatum. value is represented as string. + std::map unregisteredMetas; + + Dictionary meta; // other non-buitin meta values. TODO: remove this variable + // and use `customData` instead, since pxrUSD does not allow + // non-builtin Prim metadatum + + /// + /// Update metadatum with rhs(authored metadataum only) + /// + /// @param[in] override_authored true: override this.metadataum(authored or not-authored) when rhs.metadatum is authoerd, false override only when this.metadatum is not authored and rhs.metadataum is authored. + /// + void update_from(const PrimMetas &rhs, bool override_authored = true); + + +#if 0 + // String only metadataum. + // TODO: Represent as `MetaVariable`? + std::vector stringData; +#endif + + // FIXME: Find a better way to detect Prim meta is authored... + bool authored() const { + return (active || hidden || kind || customData || references || payload || + inherits || variants || variantSets || specializes || displayName || + sceneName || doc || comment || unregisteredMetas.size() || meta.size() || apiSchemas || + sdrMetadata || assetInfo || instanceable || clips); + } + + // + // Infos used indirectly. + // + + // Used to display/traverse Prim items based on this array + // USDA: By appearance. USDC: "primChildren" TokenVector field + std::vector primChildren; + + // Used to display/traverse Property items based on this array + // USDA: By appearance. USDC: "properties" TokenVector field + std::vector properties; + + nonstd::optional>> inheritPaths; + + nonstd::optional> variantChildren; + nonstd::optional> variantSetChildren; +}; + +// For backward compatibility +using PrimMeta = PrimMetas; + +// Metadata for Property(Relationship and Attribute) +// TODO: Rename to PropMetas +struct AttrMetas { + // frequently used items + // nullopt = not specified in USD data + nonstd::optional interpolation; // 'interpolation' + nonstd::optional elementSize; // usdSkel 'elementSize' + nonstd::optional hidden; // 'hidden' + nonstd::optional comment; // `comment` + nonstd::optional customData; // `customData` + + nonstd::optional weight; // usdSkel inbetween BlendShape weight. + + // usdShade + nonstd::optional connectability; // NOTE: applies to attr + nonstd::optional outputName; // NOTE: applies to rel + nonstd::optional renderType; // NOTE: applies to prop + nonstd::optional sdrMetadata; // NOTE: applies to attr(also seen in prim meta) + + nonstd::optional displayName; // 'displayName' + + + // + // MaterialBinding + // + // Could be arbitrary token value so use `token[]` type. + // For now, either `weakerThanDescendants` or `strongerThanDescendants` are + // valid token. + nonstd::optional bindMaterialAs; // 'bindMaterialAs' NOTE: applies to rel. + + std::map meta; // other meta values + + // String only metadataum. + // TODO: Represent as `MetaVariable`? + std::vector stringData; + + + // + // Some handy methods for non-frequently used metadatum. + // + bool has_colorSpace() const; + value::token get_colorSpace() const; // return empty when not authored or 'colorSpace' metadataum is not token type. + + bool authored() const { + return (interpolation || elementSize || hidden || customData || weight || + connectability || outputName || renderType || sdrMetadata || displayName || bindMaterialAs || meta.size() || stringData.size()); + } +}; + +// For backward compatibility +using AttrMeta = AttrMetas; + +using PropMetas = AttrMetas; + +// TODO: Move to value-types.hh? +// +// Typed TimeSamples value +// +// double radius.timeSamples = { 0: 1.0, 1: None, 2: 3.0 } +// +// in .usd, are represented as +// +// 0: (1.0, false) +// 1: (2.0, true) +// 2: (3.0, false) +// + +template +struct TypedTimeSamples { + public: + struct Sample { + double t; + T value; + bool blocked{false}; + }; + + bool empty() const { return _samples.empty(); } + + void update() const { + std::sort(_samples.begin(), _samples.end(), + [](const Sample &a, const Sample &b) { return a.t < b.t; }); + + _dirty = false; + + return; + } + + // Get value at specified time. + // For non-interpolatable types(includes enums and unknown types) + // + // Return `Held` value even when TimeSampleInterpolationType is + // Linear. Returns nullopt when specified time is out-of-range. + template::supported(), std::nullptr_t> = nullptr> + bool get(T *dst, double t = value::TimeCode::Default(), + value::TimeSampleInterpolationType interp = + value::TimeSampleInterpolationType::Linear) const { + + (void)interp; + + if (!dst) { + return false; + } + + if (empty()) { + return false; + } + + if (_dirty) { + update(); + } + + if (value::TimeCode(t).is_default()) { + // FIXME: Use the first item for now. + // TODO: Handle bloked + (*dst) = _samples[0].value; + return true; + } else { + + if (_samples.size() == 1) { + (*dst) = _samples[0].value; + return true; + } + + auto it = std::lower_bound( + _samples.begin(), _samples.end(), t, + [](const Sample &a, double tval) { return a.t < tval; }); + + if (it == _samples.end()) { + // ??? + return false; + } + + (*dst) = it->value; + return true; + } + + } + + // TODO: Move to .cc to save compile time. + // Get value at specified time. + // Return linearly interpolated value when TimeSampleInterpolationType is + // Linear. Returns nullopt when specified time is out-of-range. + template::supported(), std::nullptr_t> = nullptr> + bool get(T *dst, double t = value::TimeCode::Default(), + value::TimeSampleInterpolationType interp = + value::TimeSampleInterpolationType::Linear) const { + if (!dst) { + return false; + } + + if (empty()) { + return false; + } + + if (_dirty) { + update(); + } + + if (value::TimeCode(t).is_default()) { + // FIXME: Use the first item for now. + // TODO: Handle bloked + (*dst) = _samples[0].value; + return true; + } else { + + if (_samples.size() == 1) { + (*dst) = _samples[0].value; + return true; + } + + auto it = std::lower_bound( + _samples.begin(), _samples.end(), t, + [](const Sample &a, double tval) { return a.t < tval; }); + + if (interp == value::TimeSampleInterpolationType::Linear) { + + // MS STL does not allow seek vector iterator before begin + // Issue #110 + const auto it_minus_1 = (it == _samples.begin()) ? _samples.begin() : (it - 1); + + size_t idx0 = size_t((std::max)( + int64_t(0), + (std::min)(int64_t(_samples.size() - 1), + int64_t(std::distance(_samples.begin(), it_minus_1))))); + size_t idx1 = + size_t((std::max)(int64_t(0), (std::min)(int64_t(_samples.size() - 1), + int64_t(idx0) + 1))); + + double tl = _samples[idx0].t; + double tu = _samples[idx1].t; + + double dt = (t - tl); + if (std::fabs(tu - tl) < std::numeric_limits::epsilon()) { + // slope is zero. + dt = 0.0; + } else { + dt /= (tu - tl); + } + + // Just in case. + dt = (std::max)(0.0, (std::min)(1.0, dt)); + + const value::Value &pv0 = _samples[idx0].value; + const value::Value &pv1 = _samples[idx1].value; + + if (pv0.type_id() != pv1.type_id()) { + // Type mismatch. + return false; + } + + // To concrete type + const T *p0 = pv0.as(); + const T *p1 = pv1.as(); + + if (!p0 || !p1) { + return false; + } + + const T p = lerp(*p0, *p1, dt); + + (*dst) = std::move(p); + return true; + } else { + if (it == _samples.end()) { + // ??? + return false; + } + + (*dst) = it->value; + return true; + } + } + + return false; + } + + void add_sample(const Sample &s) { + _samples.push_back(s); + _dirty = true; + } + + void add_sample(const double t, const T &v) { + Sample s; + s.t = t; + s.value = v; + _samples.emplace_back(s); + _dirty = true; + } + + void add_blocked_sample(const double t) { + Sample s; + s.t = t; + s.blocked = true; + _samples.emplace_back(s); + _dirty = true; + } + + const std::vector &get_samples() const { + if (_dirty) { + update(); + } + + return _samples; + } + + std::vector &samples() { + if (_dirty) { + update(); + } + + return _samples; + } + + // From typeless timesamples. + bool from_timesamples(const value::TimeSamples &ts) { + std::vector buf; + for (size_t i = 0; i < ts.size(); i++) { + if (ts.get_samples()[i].value.type_id() != value::TypeTraits::type_id()) { + return false; + } + Sample s; + s.t = ts.get_samples()[i].t; + s.blocked = ts.get_samples()[i].blocked; + if (const auto pv = ts.get_samples()[i].value.as()) { + s.value = (*pv); + } else { + return false; + } + + buf.push_back(s); + } + + + _samples = std::move(buf); + _dirty = true; + + return true; + } + + size_t size() const { + if (_dirty) { + update(); + } + return _samples.size(); + } + + private: + // Need to be sorted when looking up the value. + mutable std::vector _samples; + mutable bool _dirty{false}; +}; + +// +// Scalar or TimeSamples +// +template +struct Animatable { + public: + bool is_blocked() const { return _blocked; } + + bool is_timesamples() const { + if (is_blocked()) { + return false; + } + return !_ts.empty(); + } + + bool is_scalar() const { + if (is_blocked()) { + return false; + } + return _ts.empty(); + } + + /// + /// Get value at specific time. + /// + bool get(double t, T *v, + const value::TimeSampleInterpolationType tinerp = + value::TimeSampleInterpolationType::Linear) const { + if (!v) { + return false; + } + + if (is_blocked()) { + return false; + } else if (is_scalar()) { + (*v) = _value; + return true; + } else { // timesamples + return _ts.get(v, t, tinerp); + } + } + + /// + /// Get scalar value. + /// + bool get_scalar(T *v) const { + if (!v) { + return false; + } + + if (is_blocked()) { + return false; + } else if (is_scalar()) { + (*v) = _value; + return true; + } else { // timesamples + return false; + } + } + + // TimeSamples + // void set(double t, const T &v); + + void add_sample(const double t, const T &v) { _ts.add_sample(t, v); } + + // Add None(ValueBlock) sample to timesamples + void add_blocked_sample(const double t) { _ts.add_blocked_sample(t); } + + // Scalar + void set(const T &v) { + _value = v; + _blocked = false; + } + + const TypedTimeSamples &get_timesamples() const { return _ts; } + + Animatable() {} + + Animatable(const T &v) : _value(v) {} + + // TODO: Init with timesamples + + private: + // scalar + T _value; + bool _blocked{false}; + + // timesamples + TypedTimeSamples _ts; +}; + +/// +/// Tyeped Attribute without fallback(default) value. +/// For attribute with `uniform` qualifier or TimeSamples, or have +/// `.connect`(Connection) +/// +/// To support multiple definition of attribute(up to 2), we support both having +/// Connection and values. +/// +/// e.g. float var = 1.0 +/// float var.connect = +/// (metadata is shared) +/// +/// - `authored() = true` : Attribute value is authored(attribute is +/// described in USDA/USDC) +/// - `authored() = false` : Attribute value is not authored(not described +/// in USD). If you call `get()`, fallback value is returned. +/// +template +class TypedAttribute { + public: + static std::string type_name() { return value::TypeTraits::type_name(); } + + static uint32_t type_id() { return value::TypeTraits::type_id(); } + + TypedAttribute() = default; + + TypedAttribute &operator=(const T &value) { + _attrib = value; + _value_empty = false; + + // fallback Value should be already set with `AttribWithFallback(const T& + // fallback)` constructor. + + return (*this); + } + + void set_value(const T &v) { _attrib = v; _value_empty = false; } + + const nonstd::optional get_value() const { + if (_attrib) { + return _attrib.value(); + } + return nonstd::nullopt; + } + + bool get_value(T *dst) const { + if (!dst) return false; + + if (_attrib) { + (*dst) = _attrib.value(); + return true; + } + return false; + } + + bool is_blocked() const { return _blocked; } + + // for `uniform` attribute only + void set_blocked(bool onoff) { _blocked = onoff; if (onoff) _value_empty = false; } + + bool is_connection() const { return _paths.size(); } + + void set_connection(const Path &path) { + _paths.clear(); + _paths.push_back(path); + _value_empty = false; + } + + void set_connections(const std::vector &paths) { _paths = paths; _value_empty = false; } + + const std::vector &get_connections() const { return _paths; } + const std::vector &connections() const { return _paths; } + + const nonstd::optional get_connection() const { + if (_paths.size()) { + return _paths[0]; + } + + return nonstd::nullopt; + } + + // TODO: Supply set_connection_empty()? + + void set_value_empty() { _value_empty = true; } + + bool is_value_empty() const { + if (is_connection()) { + return false; + } + + if (_attrib.has_value()) { + return false; + } + + if (_blocked) { + return false; + } + + return _value_empty; + } + + // value set? + bool authored() const { + if (_attrib) { + return true; + } + + if (_paths.size()) { + return true; + } + + if (_value_empty) { + // Declare only. + return true; + } + + if (_blocked) { + return true; + } + + return false; + } + + void clear_connections() { + _paths.clear(); + } + + void clear_value() { + _attrib.reset(); + _value_empty = true; + } + + const AttrMeta &metas() const { return _metas; } + AttrMeta &metas() { return _metas; } + + private: + AttrMeta _metas; + bool _value_empty{false}; // applies `_attrib` + std::vector _paths; + nonstd::optional _attrib; + bool _blocked{false}; // for `uniform` attribute. +}; + +/// +/// Tyeped Terminal(Output) Attribute(No value assign, no fallback(default) +/// value, no connection) +/// +/// - `authored() = true` : Attribute value is authored(attribute is +/// described in USDA/USDC) +/// - `authored() = false` : Attribute value is not authored(not described +/// in USD). +/// +template +class TypedTerminalAttribute { + public: + void set_authored(bool onoff) { _authored = onoff; } + + // value set? + bool authored() const { return _authored; } + + static std::string type_name() { return value::TypeTraits::type_name(); } + static uint32_t type_id() { return value::TypeTraits::type_id(); } + + // Actual type is a typeName in USDA or USDC + // for example, we accect float3 type for TypedTerminalAttribute and + // print/serialize this attribute value with actual type. + // + void set_actual_type_name(const std::string &type_name) { + _actual_type_name = type_name; + } + + bool has_actual_type() const { return _actual_type_name.size(); } + + const std::string &get_actual_type_name() const { return _actual_type_name; } + + const AttrMeta &metas() const { return _metas; } + AttrMeta &metas() { return _metas; } + + private: + AttrMeta _metas; + bool _authored{false}; + std::string _actual_type_name; +}; + +template +class TypedAttributeWithFallback; + +/// +/// Attribute with fallback(default) value. +/// For attribute with `uniform` qualifier or TimeSamples, but don't have +/// `.connect`(Connection) +/// +/// - `authored() = true` : Attribute value is authored(attribute is +/// described in USDA/USDC) +/// - `authored() = false` : Attribute value is not authored(not described +/// in USD). If you call `get()`, fallback value is returned. +/// +template +class TypedAttributeWithFallback { + public: + static std::string type_name() { return value::TypeTraits::type_name(); } + static uint32_t type_id() { return value::TypeTraits::type_id(); } + + TypedAttributeWithFallback() = delete; + + /// + /// Init with fallback value; + /// + TypedAttributeWithFallback(const T &fallback) : _fallback(fallback) {} + + TypedAttributeWithFallback &operator=(const T &value) { + _attrib = value; + + // fallback Value should be already set with `AttribWithFallback(const T& + // fallback)` constructor. + + return (*this); + } + + // + // FIXME: Defininig copy constructor, move constructor and move assignment + // operator Gives compilation error :-(. so do not define it. + // + + // AttribWithFallback(const AttribWithFallback &rhs) { + // attrib = rhs.attrib; + // fallback = rhs.fallback; + // } + + // AttribWithFallback &operator=(T&& value) noexcept { + // if (this != &value) { + // attrib = std::move(value.attrib); + // fallback = std::move(value.fallback); + // } + // return (*this); + // } + + // AttribWithFallback(AttribWithFallback &&rhs) noexcept { + // if (this != &rhs) { + // attrib = std::move(rhs.attrib); + // fallback = std::move(rhs.fallback); + // } + // } + + void set_value(const T &v) { _attrib = v; } + + void set_value_empty() { _empty = true; } + + bool is_value_empty() const { return _empty; } + + const T &get_value() const { + if (_attrib) { + return _attrib.value(); + } + return _fallback; + } + + bool is_blocked() const { return _blocked; } + + // for `uniform` attribute only + void set_blocked(bool onoff) { _blocked = onoff; } + + bool is_connection() const { return _paths.size(); } + + void set_connection(const Path &path) { + _paths.clear(); + _paths.push_back(path); + } + + void set_connections(const std::vector &paths) { _paths = paths; } + + const std::vector &get_connections() const { return _paths; } + const std::vector &connections() const { return _paths; } + + const nonstd::optional get_connection() const { + if (_paths.size()) { + return _paths[0]; + } + + return nonstd::nullopt; + } + + // value set? + bool authored() const { + if (_empty) { // authored with empty value. + return true; + } + if (_attrib) { + return true; + } + if (_paths.size()) { + return true; + } + if (_blocked) { + return true; + } + return false; + } + + const AttrMeta &metas() const { return _metas; } + AttrMeta &metas() { return _metas; } + + private: + AttrMeta _metas; + std::vector _paths; + nonstd::optional _attrib; + bool _empty{false}; + T _fallback; + bool _blocked{false}; // for `uniform` attribute. +}; + +template +using TypedAnimatableAttributeWithFallback = + TypedAttributeWithFallback>; + +bool ConvertTokenAttributeToStringAttribute( + const TypedAttribute> &inp, + TypedAttribute> &out); + + +/// +/// Similar to pxrUSD's PrimIndex +/// +class PrimNode; + +#if 0 // TODO +class PrimRange +{ + public: + class iterator; + + iterator begin() const { + } + iterator end() const { + } + + private: + const PrimNode *begin_; + const PrimNode *end_; + size_t depth_{0}; +}; +#endif + +template +class ListOp { + public: + ListOp() : is_explicit(false) {} + + void ClearAndMakeExplicit() { + explicit_items.clear(); + added_items.clear(); + prepended_items.clear(); + appended_items.clear(); + deleted_items.clear(); + ordered_items.clear(); + + is_explicit = true; + } + + bool IsExplicit() const { return is_explicit; } + bool HasExplicitItems() const { return explicit_items.size(); } + + bool HasAddedItems() const { return added_items.size(); } + + bool HasPrependedItems() const { return prepended_items.size(); } + + bool HasAppendedItems() const { return appended_items.size(); } + + bool HasDeletedItems() const { return deleted_items.size(); } + + bool HasOrderedItems() const { return ordered_items.size(); } + + const std::vector &GetExplicitItems() const { return explicit_items; } + + const std::vector &GetAddedItems() const { return added_items; } + + const std::vector &GetPrependedItems() const { return prepended_items; } + + const std::vector &GetAppendedItems() const { return appended_items; } + + const std::vector &GetDeletedItems() const { return deleted_items; } + + const std::vector &GetOrderedItems() const { return ordered_items; } + + void SetExplicitItems(const std::vector &v) { explicit_items = v; } + + void SetAddedItems(const std::vector &v) { added_items = v; } + + void SetPrependedItems(const std::vector &v) { prepended_items = v; } + + void SetAppendedItems(const std::vector &v) { appended_items = v; } + + void SetDeletedItems(const std::vector &v) { deleted_items = v; } + + void SetOrderedItems(const std::vector &v) { ordered_items = v; } + + private: + bool is_explicit{false}; + std::vector explicit_items; + std::vector added_items; + std::vector prepended_items; + std::vector appended_items; + std::vector deleted_items; + std::vector ordered_items; +}; + +struct ListOpHeader { + enum Bits { + IsExplicitBit = 1 << 0, + HasExplicitItemsBit = 1 << 1, + HasAddedItemsBit = 1 << 2, + HasDeletedItemsBit = 1 << 3, + HasOrderedItemsBit = 1 << 4, + HasPrependedItemsBit = 1 << 5, + HasAppendedItemsBit = 1 << 6 + }; + + ListOpHeader() : bits(0) {} + + explicit ListOpHeader(uint8_t b) : bits(b) {} + + explicit ListOpHeader(ListOpHeader const &op) : bits(0) { + bits |= op.IsExplicit() ? IsExplicitBit : 0; + bits |= op.HasExplicitItems() ? HasExplicitItemsBit : 0; + bits |= op.HasAddedItems() ? HasAddedItemsBit : 0; + bits |= op.HasPrependedItems() ? HasPrependedItemsBit : 0; + bits |= op.HasAppendedItems() ? HasAppendedItemsBit : 0; + bits |= op.HasDeletedItems() ? HasDeletedItemsBit : 0; + bits |= op.HasOrderedItems() ? HasOrderedItemsBit : 0; + } + + bool IsExplicit() const { return bits & IsExplicitBit; } + + bool HasExplicitItems() const { return bits & HasExplicitItemsBit; } + bool HasAddedItems() const { return bits & HasAddedItemsBit; } + bool HasPrependedItems() const { return bits & HasPrependedItemsBit; } + bool HasAppendedItems() const { return bits & HasAppendedItemsBit; } + bool HasDeletedItems() const { return bits & HasDeletedItemsBit; } + bool HasOrderedItems() const { return bits & HasOrderedItemsBit; } + + uint8_t bits; +}; + +// +// Colum-major order(e.g. employed in OpenGL). +// For example, 12th([3][0]), 13th([3][1]), 14th([3][2]) element corresponds to +// the translation. +// +// template +// struct Matrix { +// T m[N][N]; +// constexpr static uint32_t n = N; +//}; + +inline void Identity(value::matrix2d *mat) { + memset(mat->m, 0, sizeof(value::matrix2d)); + for (size_t i = 0; i < 2; i++) { + mat->m[i][i] = static_cast(1); + } +} + +inline void Identity(value::matrix3d *mat) { + memset(mat->m, 0, sizeof(value::matrix3d)); + for (size_t i = 0; i < 3; i++) { + mat->m[i][i] = static_cast(1); + } +} + +inline void Identity(value::matrix4d *mat) { + memset(mat->m, 0, sizeof(value::matrix4d)); + for (size_t i = 0; i < 4; i++) { + mat->m[i][i] = static_cast(1); + } +} + +struct Extent { + value::float3 lower{{std::numeric_limits::infinity(), + std::numeric_limits::infinity(), + std::numeric_limits::infinity()}}; + + value::float3 upper{{-std::numeric_limits::infinity(), + -std::numeric_limits::infinity(), + -std::numeric_limits::infinity()}}; + + Extent() = default; + + Extent(const value::float3 &l, const value::float3 &u) : lower(l), upper(u) {} + + bool is_valid() const { + if (lower[0] > upper[0]) return false; + if (lower[1] > upper[1]) return false; + if (lower[2] > upper[2]) return false; + + return std::isfinite(lower[0]) && std::isfinite(lower[1]) && + std::isfinite(lower[2]) && std::isfinite(upper[0]) && + std::isfinite(upper[1]) && std::isfinite(upper[2]); + } + + std::array, 2> to_array() const { + std::array, 2> ret; + ret[0][0] = lower[0]; + ret[0][1] = lower[1]; + ret[0][2] = lower[2]; + ret[1][0] = upper[0]; + ret[1][1] = upper[1]; + ret[1][2] = upper[2]; + + return ret; + } + + const Extent &union_with(const value::float3 &p) { + lower[0] = (std::min)(lower[0], p[0]); + lower[1] = (std::min)(lower[1], p[1]); + lower[2] = (std::min)(lower[2], p[2]); + + upper[0] = (std::max)(upper[0], p[0]); + upper[1] = (std::max)(upper[1], p[1]); + upper[2] = (std::max)(upper[2], p[2]); + + return *this; + } + + const Extent &union_with(const value::point3f &p) { + union_with(value::float3{p.x, p.y, p.z}); + + return *this; + } + + const Extent &union_with(const Extent &box) { + lower[0] = (std::min)(lower[0], box.lower[0]); + lower[1] = (std::min)(lower[1], box.lower[1]); + lower[2] = (std::min)(lower[2], box.lower[2]); + + upper[0] = (std::max)(upper[0], box.upper[0]); + upper[1] = (std::max)(upper[1], box.upper[1]); + upper[2] = (std::max)(upper[2], box.upper[2]); + + return *this; + } +}; + +#if 0 +struct ConnectionPath { + bool is_input{false}; // true: Input connection. false: Output connection. + + Path path; // original Path information in USD + + std::string token; // token(or string) in USD + int64_t index{-1}; // corresponding array index(e.g. the array index to + // `Scene.shaders`) +}; + +// struct Connection { +// int64_t src_index{-1}; +// int64_t dest_index{-1}; +// }; +// +// using connection_id_map = +// std::unordered_map, Connection>; +#endif + +// +// Relationship(typeless property) +// +class Relationship { + public: + // NOTE: no explicit `uniform` variability for Relationship + // Relatinship have `uniform` variability implicitly. + // (in Crate, variability is encoded as `uniform`) + + // (varying?) rel myrel : DefineOnly(or empty) + // (varying?) rel myrel =
: Path + // (varying?) rel myrel = [,
, ...] : PathVector + // (varying?) rel myrel = None : ValueBlock + // + enum class Type { DefineOnly, Path, PathVector, ValueBlock }; + + Type type{Type::DefineOnly}; + Path targetPath; + std::vector targetPathVector; + ListEditQual listOpQual{ListEditQual::ResetToExplicit}; + + void set_listedit_qual(ListEditQual q) { listOpQual = q; } + ListEditQual get_listedit_qual() const { return listOpQual; } + + void set_novalue() { type = Type::DefineOnly; } + + void set(const Path &p) { + targetPath = p; + type = Type::Path; + } + + void set(const std::vector &pv) { + targetPathVector = pv; + type = Type::PathVector; + } + + void set(const value::ValueBlock &v) { + (void)v; + type = Type::ValueBlock; + } + + void set_blocked() { type = Type::ValueBlock; } + + bool has_value() const { return type != Type::DefineOnly; } + + bool is_path() const { return type == Type::Path; } + + bool is_pathvector() const { return type == Type::PathVector; } + + bool is_blocked() const { return type == Type::ValueBlock; } + + void set_varying_authored() { _varying_authored = true; } + + bool is_varying_authored() const { return _varying_authored; } + + const AttrMeta &metas() const { return _metas; } + AttrMeta &metas() { return _metas; } + + private: + AttrMeta _metas; + + // `varying` keyword is explicitly specified? + bool _varying_authored{false}; +}; + +// +// To represent Property which is explicitly Relationship(for builtin property) +// +// - When authored() +// - !has_value() => "rel material:binding" +// - has_value() => targetPath or array of targetPath. "rel material:binding = +// " or "rel material:binding = [, ]" +// - is_blocked() => "rel material:binding = None" +// +class RelationshipProperty { + public: + RelationshipProperty() = default; + + RelationshipProperty(const Relationship &rel) + : _authored(true), _relationship(rel) {} + + RelationshipProperty(const Path &p) { set(p); } + + RelationshipProperty(const std::vector &pv) { set(pv); } + + RelationshipProperty(const value::ValueBlock &v) { set(v); } + + void set_listedit_qual(ListEditQual q) { _relationship.set_listedit_qual(q); } + ListEditQual get_listedit_qual() const { + return _relationship.get_listedit_qual(); + } + + void set_authored() { _authored = true; } + + bool authored() const { return _authored; } + + // Declare-only: e.g. `rel myrel` + void set_empty() { + _relationship.set_novalue(); + _authored = true; + } + + void set(const Path &p) { + _relationship.set(p); + _authored = true; + } + + void set(const std::vector &pv) { + _relationship.set(pv); + _authored = true; + } + + void set(const value::ValueBlock &v) { + (void)v; + _relationship.set_blocked(); + _authored = true; + } + + void set_blocked() { + _relationship.set_blocked(); + _authored = true; + } + + const std::vector get_targetPaths() const { + std::vector paths; + if (_relationship.is_path()) { + paths.push_back(_relationship.targetPath); + } else if (_relationship.is_pathvector()) { + paths = _relationship.targetPathVector; + } + return paths; + } + + // TODO: Deprecate this direct access API to Relationship value? + const Relationship &relationship() const { return _relationship; } + + Relationship &relationship() { return _relationship; } + + bool has_value() const { return _relationship.has_value(); } + + bool is_blocked() const { return _relationship.is_blocked(); } + + const AttrMeta &metas() const { return _relationship.metas(); } + AttrMeta &metas() { return _relationship.metas(); } + + private: + bool _authored{false}; + Relationship _relationship; +}; + +// +// TypedConnection is a typed version of Relationship +// example: +// +// token varname.connect = +// float specular.connect = +// float specular:collection.connect = [, +// ] +// +// +template +class TypedConnection { + public: + using type = typename value::TypeTraits::value_type; + + static std::string type_name() { return value::TypeTraits::type_name(); } + + void set_listedit_qual(ListEditQual q) { _listOpQual = q; } + ListEditQual get_listedit_qual() const { return _listOpQual; } + + // Define-only: token output:surface + void set_empty() { _authored = true; } + + void set(const Path &p) { + _targetPaths.clear(); + _targetPaths.push_back(p); + _authored = true; + } + + void set(const std::vector &pv) { + _targetPaths = pv; + _authored = true; + } + + void set(const value::ValueBlock &v) { + (void)v; + _blocked = true; + _authored = true; + } + + void set_blocked() { + _blocked = true; + _authored = true; + } + + const std::vector &get_connections() const { return _targetPaths; } + + bool authored() const { return _authored; } + + bool has_value() const { return _targetPaths.size(); } + + bool is_blocked() const { return _blocked; } + + const AttrMeta &metas() const { return _metas; } + AttrMeta &metas() { return _metas; } + + private: + std::vector _targetPaths; + bool _authored{false}; + bool _blocked{false}; + AttrMeta _metas; + ListEditQual _listOpQual{ListEditQual::ResetToExplicit}; +}; + +#if 0 // Moved to value::TimeSampleInterpolationType +// Interpolator for TimeSample data +enum class TimeSampleInterpolation { + Nearest, // nearest neighbor + Linear, // lerp + // TODO: more to support... +}; +#endif + +// Attribute is a struct to hold generic attribute of a property(e.g. primvar) +// of Prim +// TODO: Refactor +class Attribute { + + public: + Attribute() = default; + + /// + /// Construct Attribute with typed value(`float`, `token`, ...). + /// + template + Attribute(const T &v, bool varying = true) { + static_assert((value::TypeId::TYPE_ID_VALUE_BEGIN <= + value::TypeTraits::type_id()) && + (value::TypeId::TYPE_ID_VALUE_END > + value::TypeTraits::type_id()), + "T is not a value type"); + set_value(v); + variability() = varying ? Variability::Varying : Variability::Uniform; + } + + /// + /// Construct uniform attribute. + /// + template + static Attribute Uniform(const T &v) { + + static_assert((value::TypeId::TYPE_ID_VALUE_BEGIN <= + value::TypeTraits::type_id()) && + (value::TypeId::TYPE_ID_VALUE_END > + value::TypeTraits::type_id()), + "T is not a value type"); + + Attribute attr; + attr.set_value(v); + attr.variability() = Variability::Uniform; + return attr; + } + + + /// + /// Construct connection attribute. + /// + Attribute(const Path &v) { + set_connection(v); + } + + Attribute(const std::vector &vs) { + set_connections(vs); + } + + const std::string &name() const { return _name; } + + std::string &name() { return _name; } + + void set_name(const std::string &name) { _name = name; } + + void set_type_name(const std::string &tname) { _type_name = tname; } + + // `var` may be empty or ValueBlock, so store type info with set_type_name and + // set_type_id. + std::string type_name() const { + if (_type_name.size()) { + return _type_name; + } + + if (!is_connection()) { + // Fallback. May be unreliable(`var` could be empty). + return _var.type_name(); + } + + return std::string(); + } + + uint32_t type_id() const { + if (_type_name.size()) { + return value::GetTypeId(_type_name); + } + + if (!is_connection()) { + // Fallback. May be unreliable(`var` could be empty). + return _var.type_id(); + } + + return value::TYPE_ID_INVALID; + } + + template + void set_value(const T &v) { + if (_type_name.empty()) { + _type_name = value::TypeTraits::type_name(); + } + _var.set_value(v); + } + + void set_var(primvar::PrimVar &v) { + if (_type_name.empty()) { + _type_name = v.type_name(); + } + + _var = v; + } + + void set_var(primvar::PrimVar &&v) { + if (_type_name.empty()) { + _type_name = v.type_name(); + } + + _var = std::move(v); + } + + /// @brief Get the value of Attribute of specified type. + /// @tparam T value type + /// @return The value if the underlying PrimVar is type T. Return + /// nonstd::nullpt when type mismatch. + template + nonstd::optional get_value() const { + return _var.get_value(); + } + + template + bool get_value(T *v) const { + if (!v) { + return false; + } + + nonstd::optional ret = _var.get_value(); + if (ret) { + (*v) = std::move(ret.value()); + return true; + } + + return false; + } + + template + void set_timesample(const T &v, double t) { + _var.set_timesample(t, v); + } + + template + bool get_value(const double t, T *dst, + value::TimeSampleInterpolationType tinterp = + value::TimeSampleInterpolationType::Linear) const { + if (!dst) { + return false; + } + + if (is_timesamples()) { + return _var.get_interpolated_value(t, tinterp, dst); + } else { + nonstd::optional v = _var.get_value(); + if (v) { + (*dst) = v.value(); + return true; + } + + return false; + } + } + + const AttrMeta &metas() const { return _metas; } + AttrMeta &metas() { return _metas; } + + const primvar::PrimVar &get_var() const { return _var; } + + void set_blocked(bool onoff) { _var.set_blocked(onoff); } + + bool is_blocked() const { return _var.is_blocked(); } + + Variability &variability() { return _variability; } + Variability variability() const { return _variability; } + + bool is_uniform() const { return _variability == Variability::Uniform; } + + void set_varying_authored() { _varying_authored = true; } + + bool is_varying_authored() const { return _varying_authored; } + + bool is_connection() const { return _paths.size(); } + + bool is_value() const { + if (is_connection()) { + return false; + } + + if (is_blocked()) { + return false; + } + + return true; + } + + bool is_timesamples() const { + if (!is_value()) { + return false; + } + + return _var.is_timesamples(); + } + + void set_connection(const Path &path) { + _paths.clear(); + _paths.push_back(path); + } + void set_connections(const std::vector &paths) { _paths = paths; } + + nonstd::optional get_connection() const { + if (_paths.size() == 1) { + return _paths[0]; + } + return nonstd::nullopt; + } + + const std::vector &connections() const { return _paths; } + std::vector &connections() { return _paths; } + + private: + std::string _name; // attrib name + Variability _variability{ + Variability::Varying}; // 'uniform` qualifier is handled with + // `variability=uniform` + + // `varying` keyword is explicitly specified? + bool _varying_authored{false}; + + // bool _blocked{false}; // Attribute Block('None') + std::string _type_name; + primvar::PrimVar _var; + std::vector _paths; + AttrMeta _metas; +}; + +// Generic container for Attribute or Relation/Connection. And has this property +// is custom or not (Need to lookup schema if the property is custom or not for +// Crate data) +// TODO: Move Connection to Attribute +// TODO: Deprecate `custom` attribute: +// https://github.com/PixarAnimationStudios/USD/issues/2069 +class Property { + public: + enum class Type { + EmptyAttrib, // Attrib with no data. + Attrib, // Attrib which contains actual data + Relation, // `rel` with targetPath(s). + NoTargetsRelation, // `rel` with no targets. + Connection, // Connection attribute(`.connect` suffix). TODO: Deprecate + // this and use Attrib. + }; + + Property() = default; + + // TODO: Deprecate this constructor. + // Property(const std::string &type_name, bool custom = false) + // : _has_custom(custom) { + // _attrib.set_type_name(type_name); + // _type = Type::EmptyAttrib; + //} + + template + Property(bool custom = false) : _has_custom(custom) { + _attrib.set_type_name(value::TypeTraits::type_name()); + _type = Type::EmptyAttrib; + } + + static Property MakeEmptyAttrib(const std::string &type_name, + bool custom = false) { + Property p; + p.set_custom(custom); + p.set_property_type(Type::EmptyAttrib); + p.attribute().set_type_name(type_name); + return p; + } + + Property(const Attribute &a, bool custom = false) + : _attrib(a), _has_custom(custom) { + _type = Type::Attrib; + } + + Property(Attribute &&a, bool custom = false) + : _attrib(std::move(a)), _has_custom(custom) { + _type = Type::Attrib; + } + + // Relationship(typeless) + Property(const Relationship &r, bool custom = false) + : _rel(r), _has_custom(custom) { + _type = Type::Relation; + set_listedit_qual(r.get_listedit_qual()); + } + + // Relationship(typeless) + Property(Relationship &&r, bool custom = false) + : _rel(std::move(r)), _has_custom(custom) { + _type = Type::Relation; + set_listedit_qual(r.get_listedit_qual()); + } + + // Attribute Connection: has type + Property(const Path &path, const std::string &prop_value_type_name, + bool custom = false) + : _prop_value_type_name(prop_value_type_name), _has_custom(custom) { + _attrib.set_connection(path); + _attrib.set_type_name(prop_value_type_name); + _type = Type::Connection; + } + + // Attribute Connection: has multiple targetPaths + Property(const std::vector &paths, + const std::string &prop_value_type_name, bool custom = false) + : _prop_value_type_name(prop_value_type_name), _has_custom(custom) { + _attrib.set_connections(paths); + _attrib.set_type_name(prop_value_type_name); + _type = Type::Connection; + } + + bool is_attribute() const { + return (_type == Type::EmptyAttrib) || (_type == Type::Attrib); + } + bool is_empty() const { + return (_type == Type::EmptyAttrib) || (_type == Type::NoTargetsRelation); + } + bool is_relationship() const { + return (_type == Type::Relation) || (_type == Type::NoTargetsRelation); + } + + // TODO: Deprecate this and use is_attribute_connection + bool is_connection() const { return _type == Type::Connection; } + + bool is_attribute_connection() const { + if (is_attribute()) { + return _attrib.is_connection(); + } + + return false; + } + + std::string value_type_name() const { + if (is_connection()) { + return _prop_value_type_name; + } else if (is_relationship()) { + // relation is typeless. + return std::string(); + } else { + return _attrib.type_name(); + } + } + + bool has_custom() const { return _has_custom; } + void set_custom(const bool onoff) { _has_custom = onoff; } + + void set_property_type(Type ty) { _type = ty; } + + Type get_property_type() const { return _type; } + + void set_listedit_qual(ListEditQual qual) { _listOpQual = qual; } + + const Attribute &get_attribute() const { return _attrib; } + + Attribute &attribute() { return _attrib; } + + void set_attribute(const Attribute &attrib) { + _attrib = attrib; + _type = Type::Attrib; + } + + const Relationship &get_relationship() const { return _rel; } + + Relationship &relationship() { return _rel; } + + /// + /// Convienient methos when Property is a Relationship + /// + + /// + /// Return single relationTarget path when Property is a Relationship. + /// Return the first path when Relationship is composed of PathVector(multiple + /// paths) + /// + nonstd::optional get_relationTarget() const { + if (!is_connection()) { + return nonstd::nullopt; + } + + if (_rel.is_path()) { + return _rel.targetPath; + } else if (_rel.is_pathvector()) { + if (_rel.targetPathVector.size() > 0) { + return _rel.targetPathVector[0]; + } + } + + return nonstd::nullopt; + } + + /// + /// Return multiple relationTarget paths when Property is a Relationship. + /// Returns empty when Property is not a Relationship or a Relationship does + /// not contain any target paths. + /// + std::vector get_relationTargets() const { + std::vector pv; + + if (!is_connection()) { + return pv; + } + + if (_rel.is_path()) { + pv.push_back(_rel.targetPath); + } else if (_rel.is_pathvector()) { + pv = _rel.targetPathVector; + } + + return pv; + } + + ListEditQual get_listedit_qual() const { return _listOpQual; } + + private: + Attribute _attrib; // attribute(value or ".connect") + + // List Edit qualifier(Attribute can never be list editable) + // TODO: Store listEdit qualifier to `Relation` + ListEditQual _listOpQual{ListEditQual::ResetToExplicit}; + + Type _type{Type::EmptyAttrib}; + Relationship _rel; // Relation(`rel`) + std::string _prop_value_type_name; // for Connection. + bool _has_custom{false}; // Qualified with 'custom' keyword? This will be + // deprecated though +}; + +struct XformOp { + enum class OpType { + // matrix + Transform, + + // vector3 + Translate, + Scale, + + // scalar + RotateX, + RotateY, + RotateZ, + + // vector3 + RotateXYZ, + RotateXZY, + RotateYXZ, + RotateYZX, + RotateZXY, + RotateZYX, + + // quaternion + Orient, + + // Special token + ResetXformStack, // !resetXformStack! + }; + + // OpType op; + OpType op_type; + bool inverted{false}; // true when `!inverted!` prefix + std::string + suffix; // may contain nested namespaces. e.g. suffix will be + // ":blender:pivot" for "xformOp:translate:blender:pivot". Suffix + // will be empty for "xformOp:translate" + + primvar::PrimVar _var; + // const value::TimeSamples &get_ts() const { return _var.ts_raw(); } + + std::string get_value_type_name() const { return _var.type_name(); } + + uint32_t get_value_type_id() const { return _var.type_id(); } + + // TODO: Check if T is valid type. + template + void set_value(const T &v) { + _var.set_value(v); + } + + template + void set_timesample(const float t, const T &v) { + _var.set_timesample(t, v); + } + + void set_timesamples(const value::TimeSamples &v) { _var.set_timesamples(v); } + + void set_timesamples(value::TimeSamples &&v) { _var.set_timesamples(v); } + + bool is_timesamples() const { return _var.is_timesamples(); } + + nonstd::optional get_timesamples() const { + if (is_timesamples()) { + return _var.ts_raw(); + } + return nonstd::nullopt; + } + + nonstd::optional get_scalar() const { + if (is_timesamples()) { + return nonstd::nullopt; + } + return _var.value_raw(); + } + + // Type-safe way to get concrete value. + template + nonstd::optional get_value() const { + if (is_timesamples()) { + return nonstd::nullopt; + } + + return _var.get_value(); + } + + const primvar::PrimVar &get_var() const { return _var; } + + primvar::PrimVar &var() { return _var; } +}; + +// forward decl +class MaterialBinding; +struct Model; +class Prim; +class PrimSpec; + +// TODO: deprecate this and use PrimSpec for variantSet statement. +// Variant item in VariantSet. +// Variant can contain Prim metas, Prim tree and properties. +struct Variant { + // const std::string &name() const { return _name; } + // std::string &name() { return _name; } + + const PrimMeta &metas() const { return _metas; } + PrimMeta &metas() { return _metas; } + + std::map &properties() { return _props; } + const std::map &properties() const { return _props; } + + const std::vector &primChildren() const { return _primChildren; } + std::vector &primChildren() { return _primChildren; } + + private: + // std::vector primIndices; + std::map _props; + + // std::string _name; // variant name + PrimMeta _metas; + + // We represent Prim children as `Prim` for a while. + // TODO: Use PrimNode or PrimSpec? + std::vector _primChildren; +}; + + +struct VariantSet { + // variantSet name = { + // "variant1" ... + // "variant2" ... + // ... + // } + + std::string name; + std::map variantSet; +}; + +// For variantSet statement in PrimSpec(composition). +struct VariantSetSpec +{ + std::string name; + std::map variantSet; +}; + +// Collection API +// https://openusd.org/release/api/class_usd_collection_a_p_i.html + +constexpr auto kExpandPrims = "expandPrims"; +constexpr auto kExplicitOnly = "explicitOnly"; +constexpr auto kExpandPrimsAndProperties = "expandPrimsAndProperties"; + +struct CollectionInstance { + + enum class ExpansionRule { + ExpandPrims, // "expandPrims" (default) + ExplicitOnly, // "explicitOnly" + ExpandPrimsAndProperties, // "expandPrimsAndProperties" + }; + + TypedAttributeWithFallback expansionRule{ExpansionRule::ExpandPrims}; // uniform token collection:collectionName:expansionRule + TypedAttributeWithFallback> includeRoot{false}; // bool collection::includeRoot + nonstd::optional includes; // rel collection::includes + nonstd::optional excludes; // rel collection::excludes + +}; + +class Collection +{ + public: + const ordered_dict instances() const { + return _instances; + } + + bool add_instance(const std::string &name, CollectionInstance &instance) { + if (_instances.count(name)) { + return false; + } + + _instances.insert(name, instance); + + return true; + } + + bool get_instance(const std::string &name, const CollectionInstance **coll) const { + if (!coll) { + return false; + } + + return _instances.at(name, coll); + } + + CollectionInstance &get_or_add_instance(const std::string &name) { + return _instances.get_or_add(name); + } + + bool has_instance(const std::string &name) const { + return _instances.count(name); + } + + bool del_instance(const std::string &name) { + return _instances.erase(name); + } + + private: + ordered_dict _instances; +}; + +// for bindMaterialAs +constexpr auto kWeaderThanDescendants = "weakerThanDescendants"; +constexpr auto kStrongerThanDescendants = "strongerThanDescendants"; + +enum class MaterialBindingStrength +{ + WeakerThanDescendants, // default + StrongerThanDescendants +}; + +// TODO: Move to pprinter.hh? +std::string to_string(const MaterialBindingStrength strength); + +class MaterialBinding { + public: + + static value::token kAllPurpose() { + return value::token(""); + } + + // + // NOTE on material binding. + // https://openusd.org/release/wp_usdshade.html + // + // - "all purpose", direct binding, material:binding. single relationship target only + // - a purpose-restricted, direct, fallback binding, e.g. material:binding:preview + // - an all-purpose, collection-based binding, e.g. material:binding:collection:metalBits + // - a purpose-restricted, collection-based binding, e.g. material:binding:collection:full:metalBits + // + // In TinyUSDZ, treat empty purpose token as "all purpose" + // + + // Some frequently used materialBindings + nonstd::optional materialBinding; // material:binding + nonstd::optional materialBindingPreview; // material:binding:preview + nonstd::optional materialBindingFull; // material:binding:full + + //nonstd::optional materialBindingCollection; // material:binding:collection Deprecated. use materialBindingCollectionMap[""][""] instead. + + value::token get_materialBindingStrength(const value::token &purpose); + value::token get_materialBindingStrengthCollection(const value::token &collection_name, const value::token &purpose); + + bool has_materialBinding() const { + return materialBinding.has_value(); + } + + bool has_materialBindingPreview() const { + return materialBindingPreview.has_value(); + } + + bool has_materialBindingFull() const { + return materialBindingFull.has_value(); + } + + bool has_materialBinding(const value::token &mat_purpose) const { + if (mat_purpose.str() == kAllPurpose().str()) { + return has_materialBinding(); + } else if (mat_purpose.str() == "full") { + return has_materialBindingFull(); + } else if (mat_purpose.str() == "preview") { + return has_materialBindingPreview(); + } else { + return _materialBindingMap.count(mat_purpose.str()); + } + } + + void clear_materialBinding() { + materialBinding.reset(); + } + + void clear_materialBindingPreview() { + materialBindingPreview.reset(); + } + + void clear_materialBindingFull() { + materialBindingFull.reset(); + } + + void set_materialBinding(const Relationship &rel) { + materialBinding = rel; + } + + void set_materialBinding(const Relationship &rel, const MaterialBindingStrength strength) { + value::token strength_tok(to_string(strength)); + materialBinding = rel; + materialBinding.value().metas().bindMaterialAs = strength_tok; + } + + void set_materialBindingPreview(const Relationship &rel) { + materialBindingPreview = rel; + } + + void set_materialBindingPreview(const Relationship &rel, const MaterialBindingStrength strength) { + value::token strength_tok(to_string(strength)); + materialBindingPreview = rel; + materialBindingPreview.value().metas().bindMaterialAs = strength_tok; + } + + void set_materialBindingFull(const Relationship &rel) { + materialBindingFull = rel; + } + + void set_materialBindingFull(const Relationship &rel, const MaterialBindingStrength strength) { + value::token strength_tok(to_string(strength)); + materialBindingFull = rel; + materialBindingFull.value().metas().bindMaterialAs = strength_tok; + } + + void set_materialBinding(const Relationship &rel, const value::token &mat_purpose) { + + if (mat_purpose.str().empty()) { + return set_materialBinding(rel); + } else if (mat_purpose.str() == "full") { + return set_materialBindingFull(rel); + } else if (mat_purpose.str() == "preview") { + return set_materialBindingFull(rel); + } else { + _materialBindingMap[mat_purpose.str()] = rel; + } + } + + void set_materialBinding(const Relationship &rel, const value::token &mat_purpose, const MaterialBindingStrength strength) { + value::token strength_tok(to_string(strength)); + + if (mat_purpose.str().empty()) { + return set_materialBinding(rel, strength); + } else if (mat_purpose.str() == "full") { + return set_materialBindingFull(rel, strength); + } else if (mat_purpose.str() == "preview") { + return set_materialBindingFull(rel, strength); + } else { + _materialBindingMap[mat_purpose.str()] = rel; + _materialBindingMap[mat_purpose.str()].metas().bindMaterialAs = strength_tok; + } + } + + bool has_materialBindingCollection(const std::string &tok) { + + if (!_materialBindingCollectionMap.count(tok)) { + return false; + } + + return _materialBindingCollectionMap.count(tok); + } + + void set_materialBindingCollection(const value::token &tok, const value::token &mat_purpose, const Relationship &rel) { + + // NOTE: + // https://openusd.org/release/wp_usdshade.html#basic-proposal-for-collection-based-assignment + // says: material:binding:collection defines a namespace of binding relationships to be applied in namespace order, with the earliest ordered binding relationship the strongest + // + // so the app is better first check if `tok` element alreasy exists(using has_materialBindingCollection) + + auto &m = _materialBindingCollectionMap[tok.str()]; + + m.insert(mat_purpose.str(), rel); + } + + void clear_materialBindingCollection(const value::token &tok, const value::token &mat_purpose) { + if (_materialBindingCollectionMap.count(tok.str())) { + _materialBindingCollectionMap[tok.str()].erase(mat_purpose.str()); + } + } + + void set_materialBindingCollection(const value::token &tok, const value::token &mat_purpose, const Relationship &rel, MaterialBindingStrength strength) { + value::token strength_tok(to_string(strength)); + + Relationship r = rel; + r.metas().bindMaterialAs = strength_tok; + + _materialBindingCollectionMap[tok.str()].insert(mat_purpose.str(), r); + } + + const std::map &materialBindingMap() const { + return _materialBindingMap; + } + + const std::map> &materialBindingCollectionMap() const { + return _materialBindingCollectionMap; + } + + bool get_materialBinding(const value::token &mat_purpose, Relationship *relOut) const { + if (!relOut) { + return false; + } + + if (mat_purpose.str().empty()) { + if (materialBinding.has_value()) { + (*relOut) = materialBinding.value(); + return true; + } else { + return false; // not authored + } + } else if (mat_purpose.str() == "full") { + if (materialBindingFull.has_value()) { + (*relOut) = materialBindingFull.value(); + return true; + } else { + return false; // not authored + } + } else if (mat_purpose.str() == "preview") { + if (materialBindingPreview.has_value()) { + (*relOut) = materialBindingPreview.value(); + return true; + } else { + return false; // not authored + } + } else { + if (_materialBindingMap.count(mat_purpose.str())) { + (*relOut) = _materialBindingMap.at(mat_purpose.str()); + return true; + } else { + return false; // not authored + } + } + } + + private: + + // For material:binding(excludes frequently used `material:binding`, `material:binding:full` and `material:binding:preview`) + // key = PURPOSE, value = rel + std::map _materialBindingMap; + + // For material:binding:collection + // Use ordered dict since the requests: + // + // https://openusd.org/release/wp_usdshade.html#basic-proposal-for-collection-based-assignment + // + // `...with the earliest ordered binding relationship the strongest` + // + // key = PURPOSE, value = map + // TODO: Use multi-index map + std::map> _materialBindingCollectionMap; +}; + +// Generic primspec container. +// Unknown or unsupported Prim type are also reprenseted as Model for now. +struct Model : public Collection, MaterialBinding { + std::string name; + + std::string prim_type_name; // e.g. "" for `def "bora" {}`, "UnknownPrim" for + // `def UnknownPrim "bora" {}` + Specifier spec{Specifier::Def}; + + int64_t parent_id{-1}; // Index to parent node + + PrimMeta meta; + + std::pair> references; + std::pair> payload; + + // std::map variantSets; + + std::map props; + + const std::vector &primChildrenNames() const { + return _primChildren; + } + const std::vector &propertyNames() const { return _properties; } + std::vector &primChildrenNames() { return _primChildren; } + std::vector &propertyNames() { return _properties; } + + private: + std::vector _primChildren; + std::vector _properties; +}; + +#if 0 // TODO: Remove +// Generic "class" Node +// Mostly identical to GPrim +struct Klass { + std::string name; + int64_t parent_id{-1}; // Index to parent node + + std::vector> references; + + std::map props; +}; +#endif + +// +// Predefined node classes +// + +// USDZ Schemas for AR +// https://developer.apple.com/documentation/arkit/usdz_schemas_for_ar/schema_definitions_for_third-party_digital_content_creation_dcc + +// UsdPhysics +struct Preliminary_PhysicsGravitationalForce { + // physics::gravitatioalForce::acceleration + value::double3 acceleration{{0.0, -9.81, 0.0}}; // [m/s^2] +}; + +struct Preliminary_PhysicsMaterialAPI { + // preliminary:physics:material:restitution + double restitution; // [0.0, 1.0] + + // preliminary:physics:material:friction:static + double friction_static; + + // preliminary:physics:material:friction:dynamic + double friction_dynamic; +}; + +struct Preliminary_PhysicsRigidBodyAPI { + // preliminary:physics:rigidBody:mass + double mass{1.0}; + + // preliminary:physics:rigidBody:initiallyActive + bool initiallyActive{true}; +}; + +struct Preliminary_PhysicsColliderAPI { + // preliminary::physics::collider::convexShape + Path convexShape; +}; + +struct Preliminary_InfiniteColliderPlane { + value::double3 position{{0.0, 0.0, 0.0}}; + value::double3 normal{{0.0, 0.0, 0.0}}; + + Extent extent; // [-FLT_MAX, FLT_MAX] + + Preliminary_InfiniteColliderPlane() { + extent.lower[0] = -(std::numeric_limits::max)(); + extent.lower[1] = -(std::numeric_limits::max)(); + extent.lower[2] = -(std::numeric_limits::max)(); + extent.upper[0] = (std::numeric_limits::max)(); + extent.upper[1] = (std::numeric_limits::max)(); + extent.upper[2] = (std::numeric_limits::max)(); + } +}; + +// UsdInteractive +struct Preliminary_AnchoringAPI { + // preliminary:anchoring:type + std::string type; // "plane", "image", "face", "none"; + + std::string alignment; // "horizontal", "vertical", "any"; + + Path referenceImage; +}; + +struct Preliminary_ReferenceImage { + int64_t image_id{-1}; // asset image + + double physicalWidth{0.0}; +}; + +struct Preliminary_Behavior { + Path triggers; + Path actions; + bool exclusive{false}; +}; + +struct Preliminary_Trigger { + // uniform token info:id + std::string info; // Store decoded string from token id +}; + +struct Preliminary_Action { + // uniform token info:id + std::string info; // Store decoded string from token id + + std::string multiplePerformOperation{ + "ignore"}; // ["ignore", "allow", "stop"] +}; + +struct Preliminary_Text { + std::string content; + std::vector font; // An array of font names + + float pointSize{144.0f}; + float width; + float height; + float depth{0.0f}; + + std::string wrapMode{"flowing"}; // ["singleLine", "hardBreaks", "flowing"] + std::string horizontalAlignmment{ + "center"}; // ["left", "center", "right", "justified"] + std::string verticalAlignmment{ + "middle"}; // ["top", "middle", "lowerMiddle", "baseline", "bottom"] +}; + +// Simple volume class. +// Currently this is just an placeholder. Not implemented. + +struct OpenVDBAsset { + std::string fieldDataType{"float"}; + std::string fieldName{"density"}; + std::string filePath; // asset +}; + +// MagicaVoxel Vox +struct VoxAsset { + std::string fieldDataType{"float"}; + std::string fieldName{"density"}; + std::string filePath; // asset +}; + +struct Volume { + OpenVDBAsset vdb; + VoxAsset vox; +}; + +// `Scope` is uncommon in graphics community, its something like `Group`. +// From USD doc: Scope is the simplest grouping primitive, and does not carry +// the baggage of transformability. +struct Scope : Collection, MaterialBinding { + std::string name; + Specifier spec{Specifier::Def}; + + int64_t parent_id{-1}; + + PrimMeta meta; + + TypedAttributeWithFallback> visibility{Visibility::Inherited}; + Purpose purpose{Purpose::Default}; + + std::map variantSet; + + std::map props; + + const std::vector &primChildrenNames() const { + return _primChildren; + } + const std::vector &propertyNames() const { return _properties; } + std::vector &primChildrenNames() { return _primChildren; } + std::vector &propertyNames() { return _properties; } + + private: + std::vector _primChildren; + std::vector _properties; +}; + +/// +/// Get elementName from Prim(e.g., Xform::name, GeomMesh::name) +/// `v` must be the value of Prim class. +/// +nonstd::optional GetPrimElementName(const value::Value &v); + +/// +/// Set name for Prim `v`(e.g. Xform::name = elementName) +/// `v` must be the value of Prim class. +/// +bool SetPrimElementName(value::Value &v, const std::string &elementName); + +// +// For `Stage` scene graph. +// Its a freezed state of an element of a scene graph(so no Prim +// additin/deletion from a scene graph is considered). May be Similar to `Prim` +// in pxrUSD. If you want to manipulate scene graph, use PrimSpec instead(but +// PrimSpec is W.I.P.) This class uses tree-representation of `Prim`. Easy to +// use, but may not be performant than flattened array index representation of +// Prim tree(Index-based scene graph such like glTF). +// +class Prim { + public: + // elementName is read from `rhs`(if it is a class of Prim) + Prim(const value::Value &rhs); + Prim(value::Value &&rhs); + + Prim(const std::string &elementName, const value::Value &rhs); + Prim(const std::string &elementName, value::Value &&rhs); + + template + Prim(const T &prim) { + set_primdata(prim); + } + + template + Prim(const std::string &elementName, const T &prim) { + set_primdata(elementName, prim); + } + + // Replace exting prim + template + void set_primdata(const T &prim) { + // Check if T is Prim class type. + static_assert((value::TypeId::TYPE_ID_MODEL_BEGIN <= + value::TypeTraits::type_id()) && + (value::TypeId::TYPE_ID_MODEL_END > + value::TypeTraits::type_id()), + "T is not a Prim class type"); + _data = prim; + // Use prim.name for elementName + _elementPath = Path(prim.name, ""); + } + + // Replace exting prim + template + void set_primdata(const std::string &elementName, const T &prim) { + // Check if T is Prim class type. + static_assert((value::TypeId::TYPE_ID_MODEL_BEGIN <= + value::TypeTraits::type_id()) && + (value::TypeId::TYPE_ID_MODEL_END > + value::TypeTraits::type_id()), + "T is not a Prim class type"); + _data = prim; + SetPrimElementName(_data, elementName); + _elementPath = Path(elementName, ""); + } + + /// + /// Add Prim as a child. + /// When `rename_element_name` is true, rename input Prims elementName to make + /// it unique among children(since USD(Crate) spec doesn't allow same Prim + /// elementName in the same Prim hierarchy. + /// + /// Renaming rule is Maya-like: + /// - No elementName given: `default` + /// - Add or increment number suffix to the elementName: + /// - `plane` => `plane1` + /// - `plane1` => `plane2` + /// + /// Note: This function is thread-safe. + /// + /// @return true Upon success. false when failed(e.g. Prim with same + /// Prim::element_name() already exists when `rename_element_name` is false) + /// and fill `err` with error message + /// + bool add_child(Prim &&prim, const bool rename_element_name = true, + std::string *err = nullptr); + + /// + /// Replace existing child Prim whose elementName is `child_prim_name`. + /// When there is no child Prim with elementName `child_prim_name` exists, + /// `prim` is added and rename is elementName to `child_prim_name`. + /// + /// @return true Upon success. false when failed(e.g. `child_prim_name` is + /// empty string or invalid Prim name) and fill `err` with error message. + /// + bool replace_child(const std::string &child_prim_name, Prim &&prim, + std::string *err = nullptr); + +#if 0 + /// + /// Add Prim as a child. + /// + /// + /// @return true Upon success. false when failed(e.g. Prim with same Prim::element_name() already exists) and fill `err` with error message + /// + /// Note: This function is thread-safe. + /// + bool add_child(Prim &&prim, const std::string &basename, std::string *err = nullptr); +#endif + + //{ + // + // _children.emplace_back(std::move(prim)); + // _child_dirty = true; + //} + + // TODO: Deprecate this API to disallow direct modification of children. + std::vector &children() { return _children; } + + const std::vector &children() const { return _children; } + + const value::Value &data() const { return _data; } + value::Value &get_data() { return _data; } + + Specifier &specifier() { return _specifier; } + + Specifier specifier() const { return _specifier; } + + // local_path is reserved for Prim composition. + // for a while please use absolute_path(full Prim absolute path) or + // element_name(leaf Prim name). + Path &local_path() { return _path; } + const Path &local_path() const { return _path; } + + /// + /// Absolute Prim Path(e.g. "/xform/mesh0") is available after + /// Stage::compute_absolute_path() or assign it manually by an app. + /// + Path &absolute_path() { return _abs_path; } + const Path &absolute_path() const { return _abs_path; } + + Path &element_path() { return _elementPath; } + const Path &element_path() const { return _elementPath; } + + // elementName = element_path's prim part + const std::string &element_name() const { return _elementPath.prim_part(); } + + const std::string type_name() const { return _data.type_name(); } + + uint32_t type_id() const { return _data.type_id(); } + + std::string &prim_type_name() { return _prim_type_name; } + const std::string &prim_type_name() const { return _prim_type_name; } + + template + bool is() const { + return (_data.type_id() == value::TypeTraits::type_id()); + } + + // Return a pointer of a concrete Prim class(Xform, Material, ...) + // Return nullptr when failed to cast or T is not a Prim type. + template + const T *as() const { + // Check if T is Prim type. e.g. Xform, Material, ... + if ((value::TypeId::TYPE_ID_MODEL_BEGIN <= + value::TypeTraits::type_id()) && + (value::TypeId::TYPE_ID_MODEL_END > value::TypeTraits::type_id())) { + return _data.as(); + } + + return nullptr; + } + +#if 0 + // Compute or update world matrix of this Prim. + // Will traverse child Prims. + void update_world_matrix(const value::matrix4d &parent_mat); + + const value::matrix4d &get_local_matrix() const; + const value::matrix4d &get_world_matrix() const; +#endif + + const PrimMeta &metas() const; + PrimMeta &metas(); + + int64_t prim_id() const { return _prim_id; } + + int64_t &prim_id() { return _prim_id; } + + const std::map &variantSets() const { + return _variantSets; + } + + std::map &variantSets() { return _variantSets; } + + /// + /// Get indices for children(). + /// + /// This is an utility API to traverse child Prims according to `primChildren` + /// Prim metadata. If you want to traverse child Prims as done in pxrUSD(which + /// used `primChildren` to determine the order of traversal), use this + /// function. + /// + /// If no `primChildren` Prim metadata, it will simply returns [0, + /// children().size()) sequence. + /// + /// index may have -1, which means invalid(child Prim not found described in + /// by primChildren) Also, app should extra check of the value of index if + /// `indices_is_valid` is set to false(index may be duplicated(Duplicated Prim + /// name exits in `primChildren`) and not in range `[0, children() -1`) + /// + /// NOTE: This function build a cache. + /// + /// @param[in] force_update Always rebuild child_indices. false = use cache if + /// exits. + /// @param[out] indices_is_valid Optional. Set true when returned indices are + /// valid. + /// + const std::vector &get_child_indices_from_primChildren( + bool force_update = true, bool *indices_is_valid = nullptr) const; + + // TODO: Add API to get parent Prim directly? + // (Currently we need to traverse parent Prim using Stage) + + private: + Path _abs_path; // Absolute Prim path in a freezed(after composition state). + // Usually set by Stage::compute_absolute_path() + Path _path; // Prim's local path name. May contain Property, Relationship and + // other infos, but do not include parent's path. To get fully + // absolute path of a Prim(e.g. "/xform0/mymesh0", You need to + // traverse Prim tree and concatename `elementPath` or use + // ***(T.B.D>) method in `Stage` class + Path _elementPath; // leaf("terminal") Prim name.(e.g. "myxform" for `def + // Xform "myform"`). For root node, elementPath name is + // empty string(""). + + std::string _prim_type_name; // Prim's type name. e.g. "Xform", "Mesh", + // "UnknownPrim", ... Could be empty for `def + // "myprim" {}` + + Specifier _specifier{ + Specifier::Invalid}; // `def`, `over` or `class`. Usually `def` + value::Value + _data; // Generic container for concrete Prim object. GPrim, Xform, ... + + std::vector _children; // child Prim nodes + // std::set _childrenNames; // child Prim name(elementName). + std::multiset + _childrenNameSet; // Stores input child Prim's elementName to assign + // unique elementName in `add_child` + + mutable bool _child_dirty{false}; + mutable bool _primChildrenIndicesIsValid{ + false}; // true when indices in _primChildrenIndices are not -1, unique, + // and index value are within [0, children().size()), and also + // _primChildrenIndices.size() == children().size() + mutable std::vector + _primChildrenIndices; // Get corresponding array index in _children, + // based on `metas().primChildren` token[] info. -1 + // = invalid. + + int64_t _prim_id{ + -1}; // Unique Prim id when positive(starts with 1). Id is assigned by + // Stage::compute_absolute_prim_path_and_assign_prim_id. Usually [1, + // NumPrimsInStage) + + std::map _variantSets; + +#if defined(TINYUSDZ_ENABLE_THREAD) + mutable std::mutex _mutex; +#endif +}; + +bool IsXformablePrim(const Prim &prim); + +// forward decl(xform.hh) +struct Xformable; +bool CastToXformable(const Prim &prim, const Xformable **xformable); + +/// +/// Get Prim's local transform(xformOps) at specified time. +/// For non-Xformable Prim it returns identity matrix. +/// +/// @param[in] prim Prim +/// @param[out] resetXformStack Whether Prim's xformOps contains +/// `!resetXformStack!` or not +/// @param[in] t time +/// @param[in] tinterp Interpolation type(Linear or Held) +/// +value::matrix4d GetLocalTransform(const Prim &prim, bool *resetXformStak, + double t = value::TimeCode::Default(), + value::TimeSampleInterpolationType tinterp = + value::TimeSampleInterpolationType::Linear); + +/// +/// TODO: Deprecate this class and use PrimPec +/// NOTE PrimNode is designed for Stage(freezed) +/// +/// Contains concrete Prim object and composition elements. +/// +/// PrimNode is near to the final state of `Prim`. +/// Doing one further step(Composition, Flatten, select Variant) to get `Prim`. +/// +/// Similar to `PrimIndex` in pxrUSD +/// + +class PrimNode { + Path path; + Path elementPath; + + PrimNode(const value::Value &rhs); + + PrimNode(value::Value &&rhs); + + value::Value prim; // GPrim, Xform, ... + + std::vector children; // child nodes + + /// + /// Select variant. + /// + bool select_variant(const std::string &target_name, + const std::string &variant_name) { + const auto m = _vsmap.find(target_name); + if (m != _vsmap.end()) { + _current_vsmap[target_name] = variant_name; + return true; + } else { + return false; + } + } + + /// + /// Get current variant selection. + /// + bool current_variant_selection(const std::string &target_name, + std::string *selected_variant_name) { + + if (!selected_variant_name) { + return false; + } + + const auto m = _vsmap.find(target_name); + if (m != _vsmap.end()) { + const auto sm = _current_vsmap.find(target_name); + if (sm != _current_vsmap.end()) { + (*selected_variant_name) = sm->second; + } else { + (*selected_variant_name) = m->second; + } + return true; + } else { + return false; + } + } + + /// + /// List variants in this Prim + /// + /// key = variant prim name + /// value = variants + /// + const VariantSelectionMap &get_variant_selection_map() const { return _vsmap; } + + /// + /// Variants + /// + /// VariantSet = Prim metas + Properties and/or child Prims + /// = repsetent as PrimNode for a while. + /// + /// + /// key = variant name + using VariantSet = std::map; + std::map varitnSetList; // key = variant + + VariantSelectionMap _vsmap; // Original variant selections + VariantSelectionMap _current_vsmap; // Currently selected variants + + std::vector primChildren; // List of child Prim nodes + std::vector properties; // List of property names + std::vector variantChildren; // List of child VariantSet nodes. +}; + +/// Similar to PrimSpec +/// PrimSpec is a Prim object state just after reading it from USDA and USDC. +/// The state before compositions and Prim reconstruction by applying +/// schema(ReconstructPrim in prim-reconstruct.hh) happens. +/// +/// Its composed primarily of name, specifier, PrimMeta and +/// Properties(Relationships and Attributes) +class PrimSpec { + public: + PrimSpec() = default; + + PrimSpec(const Specifier &spec, const std::string &name) + : _specifier(spec), _name(name) {} + PrimSpec(const Specifier &spec, const std::string &typeName, + const std::string &name) + : _specifier(spec), _typeName(typeName), _name(name) {} + + PrimSpec(const PrimSpec &rhs) { + if (this != &rhs) { + CopyFrom(rhs); + } + } + + PrimSpec &operator=(const PrimSpec &rhs) { + if (this != &rhs) { + CopyFrom(rhs); + } + + return *this; + } + + PrimSpec &operator=(PrimSpec &&rhs) { + if (this != &rhs) { + MoveFrom(rhs); + } + + return *this; + } + + const std::string &name() const { return _name; } + std::string &name() { return _name; } + + const std::string &typeName() const { return _typeName; } + // Can change type name + std::string &typeName() { return _typeName; } + + const Specifier &specifier() const { return _specifier; } + Specifier &specifier() { return _specifier; } + + const std::vector &children() const { return _children; } + std::vector &children() { return _children; } + + /// + /// Select variant. + /// + bool select_variant(const std::string &target_name, + const std::string &variant_name) { + if (metas().variants.has_value()) { + const auto m = metas().variants.value().find(target_name); + if (m != metas().variants.value().end()) { + _current_vsmap[target_name] = variant_name; + return true; + } else { + return false; + } + } + return false; + } + + bool current_variant_selection(const std::string &target_name, + std::string *selected_variant_name) { + + if (!selected_variant_name) { + return false; + } + + if (!metas().variants.has_value()) { + return false; + } + + const auto &vsmap = metas().variants.value(); + + const auto m = vsmap.find(target_name); + if (m != vsmap.end()) { + const auto sm = _current_vsmap.find(target_name); + if (sm != _current_vsmap.end()) { + (*selected_variant_name) = sm->second; + } else { + (*selected_variant_name) = m->second; + } + return true; + } else { + return false; + } + } + + /// + /// List variants in this PrimSpec + /// key = variant name + /// value = variats + /// + const VariantSelectionMap get_variant_selection_map() const { + VariantSelectionMap vsmap; + if (metas().variants.has_value()) { + vsmap = metas().variants.value(); + } + return vsmap; + } + + /// + /// Variants + /// + /// VariantSet = Prim metas + Properties and/or child Prims + /// = repsetent as PrimNode for a while. + /// + /// + /// key = variant name + std::map &variantSets() { return _variantSets; } + const std::map &variantSets() const { return _variantSets; } + + const PrimMeta &metas() const { return _metas; } + + PrimMeta &metas() { return _metas; } + + using PropertyMap = std::map; + + const PropertyMap &props() const { return _props; } + PropertyMap &props() { return _props; } + + const std::vector &get_references(); + const ListEditQual &get_references_listedit_qualifier(); + + const std::vector &get_payloads(); + const ListEditQual &get_payloads_listedit_qualifier(); + + const std::vector &primChildren() const { + return _primChildren; + } + + const std::vector &propertyNames() const { + return _properties; + } + + const std::string &get_current_working_path() const { + return _current_working_path; + } + + const std::vector &get_asset_search_paths() const { + return _asset_search_paths; + } + + void set_current_working_path(const std::string &s) { + _current_working_path = s; + } + + void set_asset_search_paths(const std::vector &search_paths) { + _asset_search_paths = search_paths; + } + + void set_asset_resolution_state( + const std::string &cwp, const std::vector &search_paths) { + _current_working_path = cwp; + _asset_search_paths = search_paths; + } + + private: + void CopyFrom(const PrimSpec &rhs) { + _specifier = rhs._specifier; + _typeName = rhs._typeName; + _name = rhs._name; + + _children = rhs._children; + + _props = rhs._props; + + //_vsmap = rhs._vsmap; + _current_vsmap = rhs._current_vsmap; + + _variantSets = rhs._variantSets; + + _primChildren = rhs._primChildren; + _properties = rhs._properties; + _variantChildren = rhs._variantChildren; + + _metas = rhs._metas; + + _current_working_path = rhs._current_working_path; + _asset_search_paths = rhs._asset_search_paths; + } + + void MoveFrom(PrimSpec &rhs) { + _specifier = std::move(rhs._specifier); + _typeName = std::move(rhs._typeName); + _name = std::move(rhs._name); + + _children = std::move(rhs._children); + + _props = std::move(rhs._props); + + //_vsmap = std::move(rhs._vsmap); + _current_vsmap = std::move(rhs._current_vsmap); + + _variantSets = std::move(rhs._variantSets); + + _primChildren = std::move(rhs._primChildren); + _properties = std::move(rhs._properties); + _variantChildren = std::move(rhs._variantChildren); + + _metas = std::move(rhs._metas); + + _current_working_path = rhs._current_working_path; + _asset_search_paths = std::move(rhs._asset_search_paths); + } + + Specifier _specifier{Specifier::Def}; + std::string _typeName; // prim's typeName(e.g. "Xform", "Material") This is + // identitical to `typeName` in Crate format) + std::string _name; // elementName. Should not be empty. + + std::vector _children; // child nodes + + PropertyMap _props; + + /// + /// Variants + /// + /// variant element = Property or Prim + /// + using PrimSpecMap = std::map; + + //VariantSelectionMap _vsmap; // Original variant selections + VariantSelectionMap _current_vsmap; // Currently selected variants + + std::map _variantSets; + + std::vector _primChildren; // List of child PrimSPec nodes + std::vector _properties; // List of property names + std::vector _variantChildren; + + PrimMeta _metas; + + /// + /// For solving asset path in nested composition. + /// Keep asset resolution state. + /// TODO: Use struct. Store userdata pointer. + /// + std::string _current_working_path; + std::vector _asset_search_paths; + +}; + +struct SubLayer +{ + value::AssetPath assetPath; + LayerOffset layerOffset; +}; + + +struct LayerMetas { + enum class PlaybackMode { + PlaybackModeNone, + PlaybackModeLoop, + }; + + // TODO: Support more predefined properties: reference = + // /pxr/usd/sdf/wrapLayer.cpp Scene global setting + TypedAttributeWithFallback upAxis{ + Axis:: + Y}; // This can be changed by plugInfo.json in USD: + // https://graphics.pixar.com/usd/dev/api/group___usd_geom_up_axis__group.html#gaf16b05f297f696c58a086dacc1e288b5 + value::token defaultPrim; // prim node name + TypedAttributeWithFallback metersPerUnit{1.0}; // default [m] + TypedAttributeWithFallback timeCodesPerSecond{ + 24.0}; // default 24 fps + TypedAttributeWithFallback framesPerSecond{24.0}; + TypedAttributeWithFallback startTimeCode{ + 0.0}; // FIXME: default = -inf? + TypedAttributeWithFallback endTimeCode{ + std::numeric_limits::infinity()}; + std::vector subLayers; // `subLayers` + value::StringData comment; // 'comment' In Stage meta, comment must be string + // only(`comment = "..."` is not allowed) + value::StringData doc; // `documentation` + + CustomDataType customLayerData; // customLayerData + + // USDZ extension + TypedAttributeWithFallback autoPlay{ + true}; // default(or not authored) = auto play + TypedAttributeWithFallback playbackMode{ + PlaybackMode::PlaybackModeLoop}; + + // Indirectly used. + std::vector primChildren; +}; + + +// Similar to SdfLayer or Stage +// It is basically hold the list of PrimSpec and Layer metadatum. +class Layer { + public: + const std::string name() const { return _name; } + + void set_name(const std::string name) { _name = name; } + + void clear_primspecs() { _prim_specs.clear(); } + + // Check if `primname` exists in root Prims? + bool has_primspec(const std::string &primname) const { + return _prim_specs.count(primname); + } + + /// + /// Add PrimSpec(copy PrimSpec instance). + /// + /// @return false when `name` already exists in `primspecs`, `name` is empty + /// string or `name` contains invalid character to be used in Prim + /// element_name. + /// + bool add_primspec(const std::string &name, const PrimSpec &ps) { + if (name.empty()) { + return false; + } + + if (!ValidatePrimElementName(name)) { + return false; + } + + if (has_primspec(name)) { + return false; + } + + _prim_specs.emplace(name, ps); + + return true; + } + + /// + /// Add PrimSpec. + /// + /// @return false when `name` already exists in `primspecs`, `name` is empty + /// string or `name` contains invalid character to be used in Prim + /// element_name. + /// + bool emplace_primspec(const std::string &name, PrimSpec &&ps) { + if (name.empty()) { + return false; + } + + if (!ValidatePrimElementName(name)) { + return false; + } + + if (has_primspec(name)) { + return false; + } + + _prim_specs.emplace(name, std::move(ps)); + + return true; + } + + /// + /// Replace PrimSpec(copy PrimSpec instance) + /// + /// @return false when `name` does not exist in `primspecs`, `name` is empty + /// string or `name` contains invalid character to be used in Prim + /// element_name. + /// + bool replace_primspec(const std::string &name, const PrimSpec &ps) { + if (name.empty()) { + return false; + } + + if (!ValidatePrimElementName(name)) { + return false; + } + + if (!has_primspec(name)) { + return false; + } + + _prim_specs.at(name) = ps; + + return true; + } + + /// + /// Replace PrimSpec + /// + /// @return false when `name` does not exist in `primspecs`, `name` is empty + /// string or `name` contains invalid character to be used in Prim + /// element_name. + /// + bool replace_primspec(const std::string &name, PrimSpec &&ps) { + if (name.empty()) { + return false; + } + + if (!ValidatePrimElementName(name)) { + return false; + } + + if (!has_primspec(name)) { + return false; + } + + _prim_specs.at(name) = std::move(ps); + + return true; + } + + const std::unordered_map &primspecs() const { + return _prim_specs; + } + + std::unordered_map &primspecs() { return _prim_specs; } + + const LayerMetas &metas() const { return _metas; } + LayerMetas &metas() { return _metas; } + + bool has_unresolved_references() const { + return _has_unresolved_references; + } + + bool has_unresolved_payload() const { + return _has_unresolved_payload; + } + + bool has_unresolved_variant() const { + return _has_unresolved_variant; + } + + bool has_over_primspec() const { + return _has_over_primspec; + } + + bool has_class_primspec() const { + return _has_class_primspec; + } + + bool has_unresolved_inherits() const { + return _has_unresolved_inherits; + } + + bool has_unresolved_specializes() const { + return _has_unresolved_specializes; + } + + /// + /// Check if PrimSpec tree contains any `references` and cache the result. + /// + /// @param[in] max_depth Maximum PrimSpec traversal depth. + /// @returns true if PrimSpec tree contains any (unresolved) `references`. false if not. + /// + bool check_unresolved_references(const uint32_t max_depth = 1024 * 1024) const; + + /// + /// Check if PrimSpec tree contains any `payload` and cache the result. + /// + /// @param[in] max_depth Maximum PrimSpec traversal depth. + /// @returns true if PrimSpec tree contains any (unresolved) `payload`. false if not. + /// + bool check_unresolved_payload(const uint32_t max_depth = 1024 * 1024) const; + + /// + /// Check if PrimSpec tree contains any `variant` and cache the result. + /// + /// @param[in] max_depth Maximum PrimSpec traversal depth. + /// @returns true if PrimSpec tree contains any (unresolved) `variant`. false if not. + /// + bool check_unresolved_variant(const uint32_t max_depth = 1024 * 1024) const; + + /// + /// Check if PrimSpec tree contains any `specializes` and cache the result. + /// + /// @param[in] max_depth Maximum PrimSpec traversal depth. + /// @returns true if PrimSpec tree contains any (unresolved) `specializes`. false if not. + /// + bool check_unresolved_specializes(const uint32_t max_depth = 1024 * 1024) const; + + /// + /// Check if PrimSpec tree contains any `inherits` and cache the result. + /// + /// @param[in] max_depth Maximum PrimSpec traversal depth. + /// @returns true if PrimSpec tree contains any (unresolved) `inherits`. false if not. + /// + bool check_unresolved_inherits(const uint32_t max_depth = 1024 * 1024) const; + + /// + /// Check if PrimSpec tree contains any Prim with `over` specifier and cache the result. + /// + /// @param[in] max_depth Maximum PrimSpec traversal depth. + /// @returns true if PrimSpec tree contains any Prim with `over` specifier. false if not. + /// + bool check_over_primspec(const uint32_t max_depth = 1024 * 1024) const; + + /// + /// Find a PrimSpec at `path` and returns it if found. + /// + /// @param[in] path PrimSpec path to find. + /// @param[out] ps Pointer to PrimSpec pointer + /// @param[out] err Error message + /// + bool find_primspec_at(const Path &path, const PrimSpec **ps, std::string *err) const; + + + /// + /// Set state for AssetResolution in the subsequent composition operation. + /// + void set_asset_resolution_state( + const std::string &cwp, const std::vector &search_paths, void *userdata=nullptr) { + _current_working_path = cwp; + _asset_search_paths = search_paths; + _asset_resolution_userdata = userdata; + } + + void get_asset_resolution_state( + std::string &cwp, std::vector &search_paths, void *&userdata) { + cwp = _current_working_path; + search_paths = _asset_search_paths; + userdata = _asset_resolution_userdata; + } + + const std::string get_current_working_path() const { + return _current_working_path; + } + + const std::vector get_asset_search_paths() const { + return _asset_search_paths; + } + + private: + std::string _name; // layer name ~= USD filename + + // key = prim name + std::unordered_map _prim_specs; + LayerMetas _metas; + +#if defined(TINYUSDZ_ENABLE_THREAD) + mutable std::mutex _mutex; +#endif + + // Cached primspec path. + // key : prim_part string (e.g. "/path/bora") + mutable std::map _primspec_path_cache; + mutable bool _dirty{true}; + + // Cached flags for composition. + // true by default even PrimSpec tree does not contain any `references`, `payload`, etc. + mutable bool _has_unresolved_references{true}; + mutable bool _has_unresolved_payload{true}; + mutable bool _has_unresolved_variant{true}; + mutable bool _has_unresolved_inherits{true}; + mutable bool _has_unresolved_specializes{true}; + mutable bool _has_over_primspec{true}; + mutable bool _has_class_primspec{true}; + + // + // Record AssetResolution state(search paths, current working directory) + // when this layer is opened by compostion(`references`, `payload`, `subLayers`) + // + mutable std::string _current_working_path; + mutable std::vector _asset_search_paths; + mutable void *_asset_resolution_userdata{nullptr}; + +}; + + +nonstd::optional InterpolationFromString(const std::string &v); +nonstd::optional OrientationFromString(const std::string &v); +nonstd::optional KindFromString(const std::string &v); + +namespace value { + +#include "define-type-trait.inc" + +DEFINE_TYPE_TRAIT(Reference, "ref", TYPE_ID_REFERENCE, 1); +DEFINE_TYPE_TRAIT(Specifier, "specifier", TYPE_ID_SPECIFIER, 1); +DEFINE_TYPE_TRAIT(Permission, "permission", TYPE_ID_PERMISSION, 1); +DEFINE_TYPE_TRAIT(Variability, "variability", TYPE_ID_VARIABILITY, 1); + +DEFINE_TYPE_TRAIT(VariantSelectionMap, "variants", TYPE_ID_VARIANT_SELECION_MAP, + 0); + +DEFINE_TYPE_TRAIT(Payload, "payload", TYPE_ID_PAYLOAD, 1); +DEFINE_TYPE_TRAIT(LayerOffset, "LayerOffset", TYPE_ID_LAYER_OFFSET, 1); + +DEFINE_TYPE_TRAIT(ListOp, "ListOpToken", TYPE_ID_LIST_OP_TOKEN, + 1); +DEFINE_TYPE_TRAIT(ListOp, "ListOpString", TYPE_ID_LIST_OP_STRING, + 1); +DEFINE_TYPE_TRAIT(ListOp, "ListOpPath", TYPE_ID_LIST_OP_PATH, 1); +DEFINE_TYPE_TRAIT(ListOp, "ListOpReference", + TYPE_ID_LIST_OP_REFERENCE, 1); +DEFINE_TYPE_TRAIT(ListOp, "ListOpInt", TYPE_ID_LIST_OP_INT, 1); +DEFINE_TYPE_TRAIT(ListOp, "ListOpUInt", TYPE_ID_LIST_OP_UINT, 1); +DEFINE_TYPE_TRAIT(ListOp, "ListOpInt64", TYPE_ID_LIST_OP_INT64, 1); +DEFINE_TYPE_TRAIT(ListOp, "ListOpUInt64", TYPE_ID_LIST_OP_UINT64, 1); +DEFINE_TYPE_TRAIT(ListOp, "ListOpPayload", TYPE_ID_LIST_OP_PAYLOAD, 1); + +DEFINE_TYPE_TRAIT(Path, "Path", TYPE_ID_PATH, 1); +DEFINE_TYPE_TRAIT(Relationship, "Relationship", TYPE_ID_RELATIONSHIP, 1); +// TODO(syoyo): Define as 1D array? +DEFINE_TYPE_TRAIT(std::vector, "PathVector", TYPE_ID_PATH_VECTOR, 1); + +DEFINE_TYPE_TRAIT(std::vector, "token[]", TYPE_ID_TOKEN_VECTOR, + 1); + +DEFINE_TYPE_TRAIT(value::TimeSamples, "TimeSamples", TYPE_ID_TIMESAMPLES, 1); + +DEFINE_TYPE_TRAIT(Collection, "Collection", TYPE_ID_COLLECTION, 1); +DEFINE_TYPE_TRAIT(CollectionInstance, "CollectionInstance", TYPE_ID_COLLECTION_INSTANCE, 1); + +DEFINE_TYPE_TRAIT(Model, "Model", TYPE_ID_MODEL, 1); +DEFINE_TYPE_TRAIT(Scope, "Scope", TYPE_ID_SCOPE, 1); + +DEFINE_TYPE_TRAIT(CustomDataType, "customData", TYPE_ID_CUSTOMDATA, + 1); // TODO: Unify with `dict`? + +DEFINE_TYPE_TRAIT(Extent, "float3[]", TYPE_ID_EXTENT, 2); // float3[2] + +#undef DEFINE_TYPE_TRAIT +#undef DEFINE_ROLE_TYPE_TRAIT + +} // namespace value + +namespace prim { + +using PropertyMap = std::map; +using ReferenceList = std::pair>; +using PayloadList = std::pair>; + +} // namespace prim + + +// TODO(syoyo): Range, Interval, Rect2i, Frustum, MultiInterval +// and Quaternion? + +/* +#define VT_GFRANGE_VALUE_TYPES \ +(( GfRange3f, Range3f )) \ +(( GfRange3d, Range3d )) \ +(( GfRange2f, Range2f )) \ +(( GfRange2d, Range2d )) \ +(( GfRange1f, Range1f )) \ +(( GfRange1d, Range1d )) + +#define VT_RANGE_VALUE_TYPES \ + VT_GFRANGE_VALUE_TYPES \ +(( GfInterval, Interval )) \ +(( GfRect2i, Rect2i )) + +#define VT_QUATERNION_VALUE_TYPES \ +(( GfQuaternion, Quaternion )) + +#define VT_NONARRAY_VALUE_TYPES \ +(( GfFrustum, Frustum)) \ +(( GfMultiInterval, MultiInterval)) + +*/ + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/primvar.cc b/contrib/tinyusdz/tinyusdz_repo/src/primvar.cc new file mode 100644 index 000000000..8e86cf925 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/primvar.cc @@ -0,0 +1,104 @@ +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +// "src/external" +//#include "external/staticstruct.hh" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// + +#include "primvar.hh" +#include "pprinter.hh" +#include "value-types.hh" +#include "value-pprint.hh" + +namespace tinyusdz { +namespace primvar { + +bool PrimVar::get_interpolated_value(const double t, const value::TimeSampleInterpolationType tinterp, value::Value *dst) const { + + if (is_blocked()) { + return false; + } + + if (is_scalar()) { + (*dst) = _value; + return true; + } + + if (is_timesamples()) { + const std::vector &samples = _ts.get_samples(); + + if (samples.empty()) { + // ??? + return false; + } + + if (value::TimeCode(t).is_default()) { + // FIXME: Use the first item for now. + if (samples[0].blocked) { + return false; + } + + (*dst) = samples[0].value; + return true; + } else { + // TODO: Unify code in prim-types.hh + auto it = std::lower_bound( + samples.begin(), samples.end(), t, + [](const value::TimeSamples::Sample &a, double tval) { return a.t < tval; }); + + if (tinterp == value::TimeSampleInterpolationType::Linear) { + + const auto it_minus_1 = (it == samples.begin()) ? samples.begin() : (it - 1); + + size_t idx0 = size_t(std::max( + int64_t(0), + std::min(int64_t(samples.size() - 1), + int64_t(std::distance(samples.begin(), it_minus_1))))); + size_t idx1 = + size_t(std::max(int64_t(0), std::min(int64_t(samples.size() - 1), + int64_t(idx0) + 1))); + + double tl = samples[idx0].t; + double tu = samples[idx1].t; + + double dt = (t - tl); + if (std::fabs(tu - tl) < std::numeric_limits::epsilon()) { + // slope is zero. + dt = 0.0; + } else { + dt /= (tu - tl); + } + + // Just in case. + dt = std::max(0.0, std::min(1.0, dt)); + + const value::Value &p0 = samples[idx0].value; + const value::Value &p1 = samples[idx1].value; + + bool ret = value::Lerp(p0, p1, dt, dst); + return ret; + } else { + if (it == samples.end()) { + // ??? + return false; + } + + (*dst) = it->value; + return true; + } + } + } + + return false; +} + +} // namespace primvar +} // namespace tinyusdz + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/primvar.hh b/contrib/tinyusdz/tinyusdz_repo/src/primvar.hh new file mode 100644 index 000000000..0be82863e --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/primvar.hh @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2021-2022 Syoyo Fujita. +// Copyright 2023-present Light Transport Entertainment Inc. + +/// +/// Type-erasure technique for Attribute/PrimVar(Primitive Variables), a Value class which can have 30+ different types(and can be compound-types(e.g. 1D/2D array, dictionary). +/// Neigher std::any nor std::variant is applicable for such usecases, so write our own, handy typesystem. +/// +/// TODO: Rename PrimVar to something more better one(AttributeValue?). +/// +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +// TODO(syoyo): Use C++17 std::optional, std::string_view when compiled with C++-17 compiler +#include "nonstd/optional.hpp" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include "value-types.hh" + +namespace tinyusdz { +namespace primvar { + +struct PrimVar { + value::Value _value{nullptr}; // For scalar(default) value + bool _blocked{false}; // ValueBlocked. + value::TimeSamples _ts; // For TimeSamples value. + + bool is_scalar() const { + return _ts.empty(); + } + + bool is_timesamples() const { + return _ts.size(); + } + + bool is_blocked() const { + // Fist check if stored value is ValueBlock, then return _blocked. + if (_value.type_id() == value::TYPE_ID_VALUEBLOCK) { + return true; + } + return _blocked; + } + + void set_blocked(bool onoff) { + // fast path + _blocked = onoff; + } + + bool is_valid() const { + if (is_timesamples()) { + return _ts.type_id() != value::TypeId::TYPE_ID_INVALID; + } else { + return _value.type_id() != value::TypeId::TYPE_ID_INVALID; + } + } + + std::string type_name() const { + if (is_timesamples()) { + return _ts.type_name(); + } else { // Assume scalar. + return _value.type_name(); + } + } + + uint32_t type_id() const { + if (!is_valid()) { + return value::TYPE_ID_INVALID; + } + + if (is_timesamples()) { + return _ts.type_id(); + } else { + return _value.type_id(); + } + } + + // Type-safe way to get concrete value for non-timesamples data. + // NOTE: This consumes lots of stack size(rougly 1000 bytes), + // If you need to handle multiple types, use as() insted. + // + template + nonstd::optional get_value() const { + + if (!is_scalar()) { + return nonstd::nullopt; + } + + return _value.get_value(); + } + + nonstd::optional get_ts_time(size_t idx) const { + + if (!is_timesamples()) { + return nonstd::nullopt; + } + + if (idx >= _ts.size()) { + return nonstd::nullopt; + } + + return _ts.get_time(idx); + } + + nonstd::optional get_timesample(size_t idx) const { + if (idx < _ts.get_samples().size()) { + return _ts.get_samples()[idx]; + } + return nonstd::nullopt; + } + + // Type-safe way to get concrete value for timesampled variable. + // No interpolation. + template + nonstd::optional get_ts_value(size_t idx) const { + + if (!is_timesamples()) { + return nonstd::nullopt; + } + + nonstd::optional pv = _ts.get_value(idx); + if (!pv) { + return nonstd::nullopt; + } + + return pv.value().get_value(); + } + + // Check if specific TimeSample value for a specified index is ValueBlock or not. + nonstd::optional is_ts_value_blocked(size_t idx) const { + + if (!is_timesamples()) { + return nonstd::nullopt; + } + + if (idx >= _ts.get_samples().size()) { + return nonstd::nullopt; + } + + return _ts.get_samples()[idx].blocked; + } + + // For Scalar only + // Returns nullptr when type-mismatch. + template + const T* as() const { + + if (!is_scalar()) { + return nullptr; + } + + return _value.as(); + } + + template + void set_value(const T &v) { + //_ts.clear(); // timeSamples and (defaut) value should co-exist. + _value = v; + } + + void set_timesamples(const value::TimeSamples &v) { + _ts = v; + } + + void set_timesamples(value::TimeSamples &&v) { + _ts = std::move(v); + } + + template + void set_timesample(double t, const T &v) { + _ts.add_sample(t, v); + } + + void set_timesample(double t, value::Value &v) { + _ts.add_sample(t, v); + } + +#if 0 // TODO + /// + /// Get typed TimesSamples + /// + template + bool get_timesamples(TypedTimeSamples *dst) { + if (!is_timesamples()) { + return false; + } + + TypedTimeSamples tss; + std::vector> buf; + for (size_t i = 0; i < ts.size(); i++) { + if (ts.get_samples()[i].value.type_id() != value::TypeTraits::type_id()) { + return false; + } + Sample s; + s.t = ts.get_samples()[i].t; + s.blocked = ts.get_samples()[i].blocked; + if (const auto pv = ts.get_samples()[i].value.as()) { + s.value = ts.get_samples()[i].value; + } else { + return false; + } + + buf.push_back(s); + } + + + _samples = std::move(buf); + _dirty = true; + + return true; + } +#endif + + + /// + /// Get interpolated timesample value. + /// + bool get_interpolated_value(const double t, const value::TimeSampleInterpolationType tinterp, value::Value *v) const; + + + template + bool get_interpolated_value(const double t, const value::TimeSampleInterpolationType tinterp, T *v) const { + return _ts.get(v, t, tinterp); + } + + size_t num_timesamples() const { + if (is_timesamples()) { + return _ts.size(); + } + return 0; + } + + const value::TimeSamples &ts_raw() const { + return _ts; + } + + value::Value &value_raw() { + return _value; + } + + const value::Value &value_raw() const { + return _value; + } + + value::TimeSamples &ts_raw() { + return _ts; + } +}; + + +} // namespace primvar +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/stage.cc b/contrib/tinyusdz/tinyusdz_repo/src/stage.cc new file mode 100644 index 000000000..2c5791dc7 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/stage.cc @@ -0,0 +1,786 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2019 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertinament Inc. + +#include +#include +// #include +#include // std::tolower +#include +#include +#include +#include +#include "asset-resolution.hh" + +// +#ifndef __wasi__ +#include +#endif +// +#include +#include +#include +#include + +#include "io-util.hh" +#include "pprinter.hh" +#include "prim-pprint.hh" +#include "str-util.hh" +#include "tiny-format.hh" +#include "tinyusdz.hh" +#include "usdLux.hh" +#include "usdShade.hh" +#include "usda-reader.hh" +#include "value-pprint.hh" +// +#include "common-macros.inc" + +namespace tinyusdz { + +#if 1 +// For PUSH_ERROR_AND_RETURN +#define PushError(s) _err += s; +// #define PushWarn(s) if (warn) { (*warn) += s; } +#endif + +namespace { + +nonstd::optional GetPrimAtPathRec(const Prim *parent, + const std::string &parent_path, + const Path &path, + const uint32_t depth) { + + if (!parent) { + return nonstd::nullopt; + } + + std::string abs_path; + // if (auto pv = GetPrimElementName(parent->data())) { + { + std::string elementName = parent->element_path().prim_part(); + // DCOUT(pprint::Indent(depth) << "Prim elementName = " << elementName); + // DCOUT(pprint::Indent(depth) << "Given Path = " << path); + // fully absolute path + abs_path = parent_path + "/" + elementName; + // DCOUT(pprint::Indent(depth) << "abs_path = " << abs_path); + // DCOUT(pprint::Indent(depth) + // << "queriying path = " << path.full_path_name()); + if (abs_path == path.full_path_name()) { + // DCOUT(pprint::Indent(depth) + // << "Got it! Found Prim at Path = " << abs_path); + return parent; + } + } + + // DCOUT(pprint::Indent(depth) + // << "# of children : " << parent->children().size()); + for (const auto &child : parent->children()) { + // const std::string &p = parent->elementPath.full_path_name(); + // DCOUT(pprint::Indent(depth + 1) << "Parent path : " << abs_path); + if (auto pv = GetPrimAtPathRec(&child, abs_path, path, depth + 1)) { + return pv.value(); + } + } + + return nonstd::nullopt; +} + +} // namespace + +// +// -- Stage +// + +nonstd::expected Stage::GetPrimAtPath( + const Path &path) const { + DCOUT("GetPrimAtPath : " << path.prim_part() << "(input path: " << path + << ")"); + + if (!path.is_valid()) { + DCOUT("Invalid path."); + return nonstd::make_unexpected("Path is invalid.\n"); + } + + if (path.is_relative_path()) { + DCOUT("Relative path is todo."); + // TODO: + return nonstd::make_unexpected("Relative path is TODO.\n"); + } + + if (!path.is_absolute_path()) { + DCOUT("Not absolute path."); + return nonstd::make_unexpected( + "Path is not absolute. Non-absolute Path is TODO.\n"); + } + + if (_dirty) { + DCOUT("clear cache."); + // Clear cache. + _prim_path_cache.clear(); + + _dirty = false; + } else { + // First find from a cache. + auto ret = _prim_path_cache.find(path.prim_part()); + if (ret != _prim_path_cache.end()) { + DCOUT("Found cache."); + return ret->second; + } + } + + + // Brute-force search. + for (const auto &parent : _root_nodes) { + if (auto pv = + GetPrimAtPathRec(&parent, /* root */ "", path, /* depth */ 0)) { + // Add to cache. + // Assume pointer address does not change unless dirty state. + _prim_path_cache[path.prim_part()] = pv.value(); + return pv.value(); + } + } + + DCOUT("Not found."); + return nonstd::make_unexpected("Cannot find path <" + path.full_path_name() + + "> int the Stage.\n"); +} + +bool Stage::find_prim_at_path(const Path &path, const Prim *&prim, + std::string *err) const { + nonstd::expected ret = GetPrimAtPath(path); + if (ret) { + prim = ret.value(); + return true; + } else { + if (err) { + (*err) = ret.error(); + } + return false; + } +} + +bool Stage::find_prim_at_path(const Path &path, int64_t *prim_id, + std::string *err) const { + if (!prim_id) { + if (err) { + (*err) = "`prim_id` argument is nullptr.\n"; + } + return false; + } + + nonstd::expected ret = GetPrimAtPath(path); + if (ret) { + (*prim_id) = ret.value()->prim_id(); + return true; + } else { + if (err) { + (*err) = ret.error(); + } + return false; + } +} + +namespace { + +bool FindPrimByPrimIdRec(uint64_t prim_id, const Prim *root, + const Prim **primFound, int level, std::string *err) { + if (level > 1024 * 1024 * 128) { + // too deep node. + return false; + } + + if (!primFound) { + return false; + } + + if (root->prim_id() == int64_t(prim_id)) { + (*primFound) = root; + return true; + } + + // Brute-force search. + for (const auto &child : root->children()) { + if (FindPrimByPrimIdRec(prim_id, &child, primFound, level + 1, err)) { + return true; + } + } + + return false; +} + +} // namespace + +bool Stage::find_prim_by_prim_id(const uint64_t prim_id, const Prim *&prim, + std::string *err) const { + if (prim_id < 1) { + if (err) { + (*err) = "Input prim_id must be 1 or greater."; + } + return false; + } + + if (_prim_id_dirty) { + DCOUT("clear prim_id cache."); + // Clear cache. + _prim_id_cache.clear(); + + _prim_id_dirty = false; + } else { + // First find from a cache. + auto ret = _prim_id_cache.find(prim_id); + if (ret != _prim_id_cache.end()) { + DCOUT("Found cache."); + return ret->second; + } + } + + const Prim *p{nullptr}; + for (const auto &root : root_prims()) { + if (FindPrimByPrimIdRec(prim_id, &root, &p, 0, err)) { + _prim_id_cache[prim_id] = p; + prim = p; + return true; + } + } + + return false; +} + +bool Stage::find_prim_by_prim_id(const uint64_t prim_id, Prim *&prim, + std::string *err) { + const Prim *c_prim{nullptr}; + if (!find_prim_by_prim_id(prim_id, c_prim, err)) { + return false; + } + + // remove const + prim = const_cast(c_prim); + + return true; +} + +nonstd::expected Stage::GetPrimFromRelativePath( + const Prim &root, const Path &path) const { + // TODO: Resolve "../" + // TODO: cache path + + if (!path.is_valid()) { + return nonstd::make_unexpected("Path is invalid.\n"); + } + + if (path.is_absolute_path()) { + return nonstd::make_unexpected( + "Path is absolute. Path must be relative.\n"); + } + + if (path.is_relative_path()) { + // ok + } else { + return nonstd::make_unexpected("Invalid Path.\n"); + } + +#if 0 // TODO + Path abs_path = root.element_path(); + abs_path.AppendElement(path.GetPrimPart()); + + DCOUT("root path = " << root.path()); + DCOUT("abs path = " << abs_path); + + // Brute-force search from Stage root. + if (auto pv = GetPrimAtPathRec(&root, /* root */"", abs_path, /* depth */0)) { + return pv.value(); + } + + return nonstd::make_unexpected("Cannot find path <" + path.full_path_name() + + "> under Prim: " + to_string(root.path) + + "\n"); +#else + (void)root; + return nonstd::make_unexpected("GetPrimFromRelativePath is TODO"); +#endif +} + +bool Stage::find_prim_from_relative_path(const Prim &root, + const Path &relative_path, + const Prim *&prim, + std::string *err) const { + nonstd::expected ret = + GetPrimFromRelativePath(root, relative_path); + if (ret) { + prim = ret.value(); + return true; + } else { + if (err) { + (*err) = ret.error(); + } + return false; + } +} + +namespace { + +#if 0 // Deprecated. TODO: remove +void PrimPrintRec(std::stringstream &ss, const Prim &prim, uint32_t indent) { + // Currently, Prim's elementName is read from name variable in concrete Prim + // class(e.g. Xform::name). + // TODO: use prim.elementPath for elementName. + std::string s = pprint_value(prim.data(), indent, /* closing_brace */ false); + + bool require_newline = true; + + // Check last 2 chars. + // if it ends with '{\n', no properties are authored so do not emit blank line + // before printing VariantSet or child Prims. + if (s.size() > 2) { + if ((s[s.size() - 2] == '{') && (s[s.size() - 1] == '\n')) { + require_newline = false; + } + } + + ss << s; + + // + // print variant + // + if (prim.variantSets().size()) { + if (require_newline) { + ss << "\n"; + } + + // need to add blank line after VariantSet stmt and before child Prims, + // so set require_newline true + require_newline = true; + + for (const auto &variantSet : prim.variantSets()) { + ss << pprint::Indent(indent + 1) << "variantSet " + << quote(variantSet.first) << " = {\n"; + + for (const auto &variantItem : variantSet.second.variantSet) { + ss << pprint::Indent(indent + 2) << quote(variantItem.first); + + const Variant &variant = variantItem.second; + + if (variant.metas().authored()) { + ss << " (\n"; + ss << print_prim_metas(variant.metas(), indent + 3); + ss << pprint::Indent(indent + 2) << ")"; + } + + ss << " {\n"; + + ss << print_props(variant.properties(), indent + 3); + + if (variant.metas().variantChildren.has_value() && + (variant.metas().variantChildren.value().size() == + variant.primChildren().size())) { + std::map primNameTable; + for (size_t i = 0; i < variant.primChildren().size(); i++) { + primNameTable.emplace(variant.primChildren()[i].element_name(), + &variant.primChildren()[i]); + } + + for (size_t i = 0; i < variant.metas().variantChildren.value().size(); + i++) { + value::token nameTok = variant.metas().variantChildren.value()[i]; + const auto it = primNameTable.find(nameTok.str()); + if (it != primNameTable.end()) { + PrimPrintRec(ss, *(it->second), indent + 3); + if (i != (variant.primChildren().size() - 1)) { + ss << "\n"; + } + } else { + // TODO: Report warning? + } + } + + } else { + for (size_t i = 0; i < variant.primChildren().size(); i++) { + PrimPrintRec(ss, variant.primChildren()[i], indent + 3); + if (i != (variant.primChildren().size() - 1)) { + ss << "\n"; + } + } + } + + ss << pprint::Indent(indent + 2) << "}\n"; + } + + ss << pprint::Indent(indent + 1) << "}\n"; + } + } + + DCOUT(prim.element_name() << " num_children = " << prim.children().size()); + + // + // primChildren + // + if (prim.children().size()) { + if (require_newline) { + ss << "\n"; + require_newline = false; + } + if (prim.metas().primChildren.size() == prim.children().size()) { + // Use primChildren info to determine the order of the traversal. + + std::map primNameTable; + for (size_t i = 0; i < prim.children().size(); i++) { + primNameTable.emplace(prim.children()[i].element_name(), + &prim.children()[i]); + } + + for (size_t i = 0; i < prim.metas().primChildren.size(); i++) { + if (i > 0) { + ss << "\n"; + } + value::token nameTok = prim.metas().primChildren[i]; + DCOUT(fmt::format("primChildren {}/{} = {}", i, + prim.metas().primChildren.size(), nameTok.str())); + const auto it = primNameTable.find(nameTok.str()); + if (it != primNameTable.end()) { + PrimPrintRec(ss, *(it->second), indent + 1); + } else { + // TODO: Report warning? + } + } + + } else { + for (size_t i = 0; i < prim.children().size(); i++) { + if (i > 0) { + ss << "\n"; + } + PrimPrintRec(ss, prim.children()[i], indent + 1); + } + } + } + + ss << pprint::Indent(indent) << "}\n"; +} +#endif + +} // namespace + +std::string Stage::ExportToString(bool relative_path) const { + (void)relative_path; // TODO + + std::stringstream ss; + + ss << "#usda 1.0\n"; + + std::stringstream meta_ss; + meta_ss << print_layer_metas(stage_metas, /* indent */1); + if (meta_ss.str().size()) { + ss << "(\n"; + ss << meta_ss.str(); + ss << ")\n"; + } + + ss << "\n"; + + if (stage_metas.primChildren.size() == _root_nodes.size()) { + std::map primNameTable; + for (size_t i = 0; i < _root_nodes.size(); i++) { + primNameTable.emplace(_root_nodes[i].element_name(), &_root_nodes[i]); + } + + for (size_t i = 0; i < stage_metas.primChildren.size(); i++) { + value::token nameTok = stage_metas.primChildren[i]; + DCOUT(fmt::format("primChildren {}/{} = {}", i, + stage_metas.primChildren.size(), nameTok.str())); + const auto it = primNameTable.find(nameTok.str()); + if (it != primNameTable.end()) { + //PrimPrintRec(ss, *(it->second), 0); + ss << prim::print_prim(*(it->second), 0); + if (i != (stage_metas.primChildren.size() - 1)) { + ss << "\n"; + } + } else { + // TODO: Report warning? + } + } + } else { + for (size_t i = 0; i < _root_nodes.size(); i++) { + //PrimPrintRec(ss, _root_nodes[i], 0); + ss << prim::print_prim(_root_nodes[i], 0); + + if (i != (_root_nodes.size() - 1)) { + ss << "\n"; + } + } + } + + return ss.str(); +} + +bool Stage::allocate_prim_id(uint64_t *prim_id) const { + if (!prim_id) { + return false; + } + + uint64_t val; + if (_prim_id_allocator.Allocate(&val)) { + (*prim_id) = val; + return true; + } + + return false; +} + +bool Stage::release_prim_id(const uint64_t prim_id) const { + return _prim_id_allocator.Release(prim_id); +} + +bool Stage::has_prim_id(const uint64_t prim_id) const { + return _prim_id_allocator.Has(prim_id); +} + +namespace { + +bool ComputeAbsPathAndAssignPrimIdRec(const Stage &stage, Prim &prim, + const Path &parentPath, uint32_t depth, + bool assign_prim_id, + bool force_assign_prim_id = true, + std::string *err = nullptr) { + if (depth > 1024 * 1024 * 128) { + // too deep node. + if (err) { + (*err) += "Prim hierarchy too deep.\n"; + } + return false; + } + + if (prim.element_name().empty()) { + // Prim's elementName must not be empty. + if (err) { + (*err) += "Prim's elementName is empty. Prim's parent Path = " + + parentPath.full_path_name() + "\n"; + } + return false; + } + + Path abs_path = parentPath.AppendPrim(prim.element_name()); + + prim.absolute_path() = abs_path; + if (assign_prim_id) { + if (force_assign_prim_id || (prim.prim_id() < 1)) { + uint64_t prim_id{0}; + if (!stage.allocate_prim_id(&prim_id)) { + if (err) { + (*err) += "Failed to assign unique Prim ID.\n"; + } + return false; + } + prim.prim_id() = int64_t(prim_id); + } + } + + for (Prim &child : prim.children()) { + if (!ComputeAbsPathAndAssignPrimIdRec(stage, child, abs_path, depth + 1, + assign_prim_id, force_assign_prim_id, + err)) { + return false; + } + } + + return true; +} + +} // namespace + +bool Stage::compute_absolute_prim_path_and_assign_prim_id( + bool force_assign_prim_id) { + Path rootPath("/", ""); + for (Prim &root : root_prims()) { + if (!ComputeAbsPathAndAssignPrimIdRec(*this, root, rootPath, 1, + /* assign_prim_id */ true, + force_assign_prim_id, &_err)) { + return false; + } + } + + // TODO: Only set dirty when prim_id changed. + _prim_id_dirty = true; + + return true; +} + +bool Stage::compute_absolute_prim_path() { + Path rootPath("/", ""); + for (Prim &root : root_prims()) { + if (!ComputeAbsPathAndAssignPrimIdRec( + *this, root, rootPath, 1, /* assign prim_id */ false, + /* force_assign_prim_id */ true, &_err)) { + return false; + } + } + + return true; +} + +bool Stage::add_root_prim(Prim &&prim, bool rename_prim_name) { + +#if defined(TINYUSDZ_ENABLE_THREAD) + // TODO: Only take a lock when dirty. + std::lock_guard lock(_mutex); +#endif + + + std::string elementName = prim.element_name(); + + if (elementName.empty()) { + if (rename_prim_name) { + + // assign default name `default` + elementName = "default"; + + if (!SetPrimElementName(prim.get_data(), elementName)) { + PUSH_ERROR_AND_RETURN("Internal error. cannot modify Prim's elementName"); + } + prim.element_path() = Path(elementName, /* prop_part */""); + } else { + PUSH_ERROR_AND_RETURN("Prim has empty elementName."); + } + } + + if (_root_nodes.size() != _root_node_nameSet.size()) { + // Rebuild nameSet + _root_node_nameSet.clear(); + for (size_t i = 0; i < _root_nodes.size(); i++) { + if (_root_nodes[i].element_name().empty()) { + PUSH_ERROR_AND_RETURN("Internal error: Existing root Prim's elementName is empty."); + } + + if (_root_node_nameSet.count(_root_nodes[i].element_name())) { + PUSH_ERROR_AND_RETURN("Internal error: Stage contains root Prim with same elementName."); + } + + _root_node_nameSet.insert(_root_nodes[i].element_name()); + } + } + + if (_root_node_nameSet.count(elementName)) { + if (rename_prim_name) { + std::string unique_name; + if (!makeUniqueName(_root_node_nameSet, elementName, &unique_name)) { + PUSH_ERROR_AND_RETURN(fmt::format("Internal error. cannot assign unique name for `{}`.\n", elementName)); + } + + elementName = unique_name; + + // Need to modify both Prim::data::name and Prim::elementPath + if (!SetPrimElementName(prim.get_data(), elementName)) { + PUSH_ERROR_AND_RETURN("Internal error. cannot modify Prim's elementName."); + } + prim.element_path() = Path(elementName, /* prop_part */""); + } else { + PUSH_ERROR_AND_RETURN(fmt::format("Prim name(elementName) {} already exists in children.\n", prim.element_name())); + } + } + + + _root_node_nameSet.insert(elementName); + _root_nodes.emplace_back(std::move(prim)); + + _dirty = true; + + return true; + + +} + +bool Stage::replace_root_prim(const std::string &prim_name, Prim &&prim) { + +#if defined(TINYUSDZ_ENABLE_THREAD) + // TODO: Only take a lock when dirty. + std::lock_guard lock(_mutex); +#endif + + if (prim_name.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("prim_name is empty.")); + } + + if (!ValidatePrimElementName(prim_name)) { + PUSH_ERROR_AND_RETURN(fmt::format("`{}` is not a valid Prim name.", prim_name)); + } + + if (_root_nodes.size() != _root_node_nameSet.size()) { + // Rebuild nameSet + _root_node_nameSet.clear(); + for (size_t i = 0; i < _root_nodes.size(); i++) { + if (_root_nodes[i].element_name().empty()) { + PUSH_ERROR_AND_RETURN("Internal error: Existing root Prim's elementName is empty."); + } + + if (_root_node_nameSet.count(_root_nodes[i].element_name())) { + PUSH_ERROR_AND_RETURN("Internal error: Stage contains root Prim with same elementName."); + } + + _root_node_nameSet.insert(_root_nodes[i].element_name()); + } + } + + // Simple linear scan + auto result = std::find_if(_root_nodes.begin(), _root_nodes.end(), [prim_name](const Prim &p) { + return (p.element_name() == prim_name); + }); + + if (result != _root_nodes.end()) { + + // Need to modify both Prim::data::name and Prim::elementPath + if (!SetPrimElementName(prim.get_data(), prim_name)) { + PUSH_ERROR_AND_RETURN("Internal error. cannot modify Prim's elementName."); + } + prim.element_path() = Path(prim_name, /* prop_part */""); + + (*result) = std::move(prim); // replace + + } else { + + // Need to modify both Prim::data::name and Prim::elementPath + if (!SetPrimElementName(prim.get_data(), prim_name)) { + PUSH_ERROR_AND_RETURN("Internal error. cannot modify Prim's elementName."); + } + prim.element_path() = Path(prim_name, /* prop_part */""); + + _root_node_nameSet.insert(prim_name); + _root_nodes.emplace_back(std::move(prim)); // add + } + + _dirty = true; + + return true; +} + +namespace { + +std::string DumpPrimTreeRec(const Prim &prim, uint32_t depth) { + std::stringstream ss; + + if (depth > 1024 * 1024 * 128) { + // too deep node. + return ss.str(); + } + + ss << pprint::Indent(depth) << "\"" << prim.element_name() << "\" " + << prim.absolute_path() << "\n"; + ss << pprint::Indent(depth + 1) << fmt::format("prim_id {}", prim.prim_id()) + << "\n"; + + for (const Prim &child : prim.children()) { + ss << DumpPrimTreeRec(child, depth + 1); + } + + return ss.str(); +} + +} // namespace + +std::string Stage::dump_prim_tree() const { + std::stringstream ss; + + for (const Prim &root : root_prims()) { + ss << DumpPrimTreeRec(root, 0); + } + return ss.str(); +} + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/stage.hh b/contrib/tinyusdz/tinyusdz_repo/src/stage.hh new file mode 100644 index 000000000..774849ac6 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/stage.hh @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: Apache 2. +// Copyright 2022 - Present, Light Transport Entertainment, Inc. +// +// Stage: Similar to Scene or Scene graph +#pragma once + +#include "composition.hh" +#include "prim-types.hh" + +#if defined(TINYUSDZ_ENABLE_THREAD) +#include +#endif + +namespace tinyusdz { + +// TODO: Use LayerMetas? +using StageMetas = LayerMetas; + +class PrimRange; + +// Similar to UsdStage, but much more something like a Scene(scene graph) +class Stage { + public: + // pxrUSD compat API ---------------------------------------- + static Stage CreateInMemory() { return Stage(); } + + /// + /// Traverse by depth-first order. + /// NOTE: Not yet implementd. Use tydra::VisitPrims() for a while. + /// + // PrimRange Traverse(); + + /// + /// Get Prim at a Path. + /// Path must be absolute Path. + /// + /// @returns Const pointer to Prim(to avoid a copy). Never returns nullptr + /// upon success. + /// + nonstd::expected GetPrimAtPath( + const Path &path) const; + + /// + /// pxrUSD Compat API + /// + bool Flatten(bool addSourceFileComment = true) const { + return compose(addSourceFileComment); + } + + /// + /// Dump Stage as ASCII(USDA) representation. + /// @param[in] relative_path (optional) Print Path as relative Path. + /// + std::string ExportToString(bool relative_path = false) const; + + // pxrUSD compat API end ------------------------------------- + + /// + /// Get Prim from a children of given root Prim. + /// Path must be relative Path. + /// + /// @returns pointer to Prim(to avoid a copy). Never return nullptr upon + /// success. + /// + nonstd::expected GetPrimFromRelativePath( + const Prim &root, const Path &path) const; + + /// Find(Get) Prim at a Path. + /// Path must be absolute Path. + /// + /// @param[in] path Absolute path(e.g. `/bora/dora`) + /// @param[out] prim const reference to Prim(if found) + /// @param[out] err Error message(filled when false is returned) + /// + /// @returns true if found a Prim. + bool find_prim_at_path(const Path &path, const Prim *&prim, + std::string *err = nullptr) const; + + /// Find(Get) Prim at a Path and returns its Prim id. + /// Path must be absolute Path. + /// + /// @param[in] path Absolute path(e.g. `/bora/dora`) + /// @param[out] prim_id Prim's id(should be '1 or greater' upon success) + /// @param[out] err Error message(filled when false is returned) + /// + /// @returns true if found a Prim. + bool find_prim_at_path(const Path &path, int64_t *prim_id, + std::string *err = nullptr) const; + + /// Find(Get) Prim from a relative Path. + /// Path must be relative Path. + /// + /// @param[in] root Find from this Prim + /// @param[in] relative_path relative path(e.g. `dora/muda`) + /// @param[out] prim const reference to the pointer to Prim(if found) + /// @param[out] err Error message(filled when false is returned) + /// + /// @returns true if found a Prim. + bool find_prim_from_relative_path(const Prim &root, const Path &relative_path, + const Prim *&prim, std::string *err) const; + + /// + /// Find(Get) Prim from Prim ID. Prim with no Prim ID assigned(-1 or 0) are + /// ignored. + /// + /// @param[in] prim_id Prim ID(1 or greater) + /// @param[out] prim const reference to the pointer to Prim(if found) + /// @param[out] err Error message(filled when false is returned) + /// + /// @returns true if found a Prim. + bool find_prim_by_prim_id(const uint64_t prim_id, const Prim *&prim, + std::string *err = nullptr) const; + + // non-const version + bool find_prim_by_prim_id(const uint64_t prim_id, Prim *&prim, + std::string *err = nullptr); + + /// + /// @brief Get Root Prims + /// + /// @return Const array of Root Prims. + /// + const std::vector &root_prims() const { return _root_nodes; } + + /// + /// @brief Reference to Root Prims array + /// + /// @return Array of Root Prims. + /// TODO: Deprecate non-const `root_prims()` API and use `add_root_prim()` instead. + /// + std::vector &root_prims() { return _root_nodes; } + + /// + /// Add Prim to root. + /// + /// @param[in] prim Prim + /// @param[in] rename_prim_name Rename Prim's elementName if required(to be unique among root Prims) + /// + /// @return true Upon success. false when failed to add Prim to root(e.g. same name exists in the root) + /// (error message can be retrieved using `get_error()`) + /// + bool add_root_prim(Prim &&prim, bool rename_prim_name = true); + + /// + /// Replace root Prim of elementName `prim_name` with `prim` + /// + /// `prim`'s elementName will be modified to `prim_name`. + /// + /// If no root prim with `prim_name` exists, `prim` is added to root Prim and rename `prim`'s elementName to `prim_name`. + /// + /// @return true Upon succes. false when failed to replace Prim at root(e.g. `prim_name` is empty). + /// + bool replace_root_prim(const std::string &prim_name, Prim &&prim); + + /// + /// @brief Get Stage metadatum + /// + /// @return Stage metadatum struct. + /// + const StageMetas &metas() const { return stage_metas; } + + StageMetas &metas() { return stage_metas; } + + /// + /// @brief Assign unique Prim id inside this Stage. + /// + /// @param[out] prim_id Allocated Primitive ID. + /// + /// @return true upon success. + /// + bool allocate_prim_id(uint64_t *prim_id) const; + + /// + /// @brief Release Prim id inside this Stage. + /// + /// @param[prim_id] prim_id Primitive ID to release(allocated by + /// `allocate_prim_id`) + /// + /// @return true upon success. false when given `prim_id` is an invalid id. + /// + bool release_prim_id(const uint64_t prim_id) const; + + /// + /// @brief Check if given prim_id exists in this Stage. + /// + /// @param[prim_id] prim_id Primitive ID to check. + /// + /// @return true if `prim_id` exists in this Stage. + /// + bool has_prim_id(const uint64_t prim_id) const; + + /// + /// @brief Commit Stage state. + /// + /// Call this function after you finished adding Prims manually(through + /// `root_prims()`) to Stage. + /// + /// (No need to call this if you just use ether USDA/USDC/USDZ reader). + /// + /// - Compute absolute path and set it to Prim::abs_path for each Prim + /// currently added to this Stage. + /// - Assign unique ID to Prim + /// + /// @param[in] force_assign_prim_id true Overwrite `prim_id` of each Prim. + /// false only assign Prim id when `prim_id` is -1(preserve user-assgiend + /// prim_id). Setting `false` is not recommended since prim_id may not be + /// unique over Prims in Stage. + /// @return false when the Stage contains any invalid Prim + /// + /// TODO: Deprecate this API an use `commit()` + bool compute_absolute_prim_path_and_assign_prim_id( + bool force_assign_prim_id = true); + + /// + /// @brief Commit Stage state. + /// + bool commit() { + // Currently we always allocate Prim ID. + return compute_absolute_prim_path_and_assign_prim_id(true); + } + + /// + /// Compute absolute Prim path for Prims in this Stage. + /// + bool compute_absolute_prim_path(); + + /// + /// Dump Prim tree info(mainly for debugging). + /// + std::string dump_prim_tree() const; + + /// + /// Compose scene(Not implemented yet). + /// + bool compose(bool addSourceFileComment = true) const; + + const std::string &get_warning() const { + return _warn; + } + + const std::string &get_error() const { + return _err; + } + + private: + +#if defined(TINYUSDZ_ENABLE_THREAD) + mutable std::mutex _mutex; +#endif + +#if 0 // Deprecated. remove. + /// + /// Loads USD from and return it as Layer + /// + /// @param[in] filename USD filename + /// @param[in] resolver AssetResolutionResolver + /// @param[out] layer Layer representation of USD data. + /// @param[in] load_states Bitmask of LoadState(optional) + /// + bool LoadLayerFromFile(const std::string &filename, const AssetResolutionResolver &resolver, Layer *layer, const uint32_t load_states = static_cast(LoadState::Toplevel)); + + /// + /// Loads USD asset from memory and return it as Layer + /// + /// @param[in] addr Memory address + /// @param[in] nbytes Num bytes + /// @param[in] asset_name Asset name(usually filename) + /// @param[out] layer Layer representation of USD data. + /// @param[in] load_states Bitmask of LoadState(optional) + /// + bool LoadLayerFromMemory(const uint8_t *addr, const size_t nbytes, const std::string &asset_name, Layer *layer, const uint32_t load_states = static_cast(LoadState::Toplevel)); +#endif + +#if 0 // Deprecated. moved to composition.hh + /// + /// Loads `reference` USD asset and return it as Layer + /// + bool LoadReference(const Reference &reference, Layer *dest); + + /// + /// Loads USD assets described in `subLayers` Stage/Layer meta and return it as Layers + /// + bool LoadSubLayers(std::vector *dest_sublayers); +#endif + + // Root nodes + std::vector _root_nodes; + std::multiset _root_node_nameSet; + + std::string name; // Scene name + int64_t default_root_node{-1}; // index to default root node + + StageMetas stage_metas; + + mutable std::string _err; + mutable std::string _warn; + + // Cached prim path. + // key : prim_part string (e.g. "/path/bora") + mutable std::map _prim_path_cache; + + // Cached prim_id -> Prim lookup + // key : prim_id + mutable std::map _prim_id_cache; + + mutable bool _dirty{true}; // True when Stage content changes(addition, deletion, composition/flatten, etc.) + + mutable bool _prim_id_dirty{true}; // True when Prim Id assignent changed(TODO: Unify with `_dirty` flag) + + mutable HandleAllocator _prim_id_allocator; +}; + +inline std::string to_string(const Stage &stage, bool relative_path = false) { + return stage.ExportToString(relative_path); +} + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/str-util.cc b/contrib/tinyusdz/tinyusdz_repo/src/str-util.cc new file mode 100644 index 000000000..c2c2f65ef --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/str-util.cc @@ -0,0 +1,678 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2023 - Present, Light Transport Entertainment, Inc. +#include "str-util.hh" + +#include "unicode-xid.hh" +#include "common-macros.inc" + +namespace tinyusdz { + +std::string buildEscapedAndQuotedStringForUSDA(const std::string &str) { + // Rule for triple quote string: + // + // if str contains newline + // if str contains """ and ''' + // use quote """ and escape " to \\", no escape for ''' + // elif str contains """ only + // use quote ''' and no escape for """ + // elif str contains ''' only + // use quote """ and no escape for ''' + // else + // use quote """ + // + // Rule for single quote string + // if str contains " and ' + // use quote " and escape " to \\", no escape for ' + // elif str contains " only + // use quote ' and no escape for " + // elif str contains ' only + // use quote " and no escape for ' + // else + // use quote " + + bool has_newline = hasNewline(str); + + std::string s; + + if (has_newline) { + bool has_triple_single_quoted_string = hasTripleQuotes(str, false); + bool has_triple_double_quoted_string = hasTripleQuotes(str, true); + + std::string delim = "\"\"\""; + if (has_triple_single_quoted_string && has_triple_double_quoted_string) { + s = escapeSingleQuote(str, true); + } else if (has_triple_single_quoted_string) { + s = escapeSingleQuote(str, false); + } else if (has_triple_double_quoted_string) { + delim = "'''"; + s = str; + } else { + s = str; + } + + s = quote(escapeControlSequence(s), delim); + + } else { + // single quote string. + bool has_single_quote = hasQuotes(str, false); + bool has_double_quote = hasQuotes(str, true); + + std::string delim = "\""; + if (has_single_quote && has_double_quote) { + s = escapeSingleQuote(str, true); + } else if (has_single_quote) { + s = escapeSingleQuote(str, false); + } else if (has_double_quote) { + delim = "'"; + s = str; + } else { + s = str; + } + + s = quote(escapeControlSequence(s), delim); + } + + return s; +} + +std::string escapeControlSequence(const std::string &str) { + std::string s; + + for (size_t i = 0; i < str.size(); i++) { + if (str[i] == '\a') { + s += "\\x07"; + } else if (str[i] == '\b') { + s += "\\x08"; + } else if (str[i] == '\t') { + s += "\\t"; + } else if (str[i] == '\v') { + s += "\\x0b"; + } else if (str[i] == '\f') { + s += "\\x0c"; + } else if (str[i] == '\\') { + // skip escaping backshash for escaped quote string: \' \" + if (i + 1 < str.size()) { + if ((str[i + 1] == '"') || (str[i + 1] == '\'')) { + s += str[i]; + } else { + s += "\\\\"; + } + } else { + s += "\\\\"; + } + } else { + s += str[i]; + } + } + + return s; +} + +std::string unescapeControlSequence(const std::string &str) { + std::string s; + + if (str.size() < 2) { + return str; + } + + for (size_t i = 0; i < str.size(); i++) { + if (str[i] == '\\') { + if (i + 1 < str.size()) { + if (str[i + 1] == 'a') { + s += '\a'; + i++; + } else if (str[i + 1] == 'b') { + s += '\b'; + i++; + } else if (str[i + 1] == 't') { + s += '\t'; + i++; + } else if (str[i + 1] == 'v') { + s += '\v'; + i++; + } else if (str[i + 1] == 'f') { + s += '\f'; + i++; + } else if (str[i + 1] == 'n') { + s += '\n'; + i++; + } else if (str[i + 1] == 'r') { + s += '\r'; + i++; + } else if (str[i + 1] == '\\') { + s += "\\"; + } else { + // ignore backslash + } + } else { + // ignore backslash + } + } else { + s += str[i]; + } + } + + return s; +} + +bool hasQuotes(const std::string &str, bool is_double_quote) { + for (size_t i = 0; i < str.size(); i++) { + if (is_double_quote) { + if (str[i] == '"') { + return true; + } + } else { + if (str[i] == '\'') { + return true; + } + } + } + + return false; +} + +bool hasTripleQuotes(const std::string &str, bool is_double_quote) { + for (size_t i = 0; i < str.size(); i++) { + if (i + 3 < str.size()) { + if (is_double_quote) { + if ((str[i + 0] == '"') && (str[i + 1] == '"') && (str[i + 2] == '"')) { + return true; + } + } else { + if ((str[i + 0] == '\'') && (str[i + 1] == '\'') && + (str[i + 2] == '\'')) { + return true; + } + } + } + } + + return false; +} + +bool hasEscapedTripleQuotes(const std::string &str, bool is_double_quote, + size_t *n) { + size_t count = 0; + + for (size_t i = 0; i < str.size(); i++) { + if (str[i] == '\\') { + if (i + 3 < str.size()) { + if (is_double_quote) { + if ((str[i + 1] == '"') && (str[i + 2] == '"') && + (str[i + 3] == '"')) { + if (!n) { // early exit + return true; + } + + count++; + i += 3; + } + } else { + if ((str[i + 1] == '\'') && (str[i + 2] == '\'') && + (str[i + 3] == '\'')) { + if (!n) { // early exit + return true; + } + count++; + i += 3; + } + } + } + } + } + + if (n) { + (*n) = count; + } + + return count > 0; +} + +std::string escapeSingleQuote(const std::string &str, + const bool is_double_quote) { + std::string s; + + if (is_double_quote) { + for (size_t i = 0; i < str.size(); i++) { + if (str[i] == '"') { + s += "\\\""; + } else { + s += str[i]; + } + } + } else { + for (size_t i = 0; i < str.size(); i++) { + if (str[i] == '\'') { + s += "\\'"; + } else { + s += str[i]; + } + } + } + + return s; +} + +std::string escapeBackslash(const std::string &str, + const bool triple_quoted_string) { + if (triple_quoted_string) { + std::string s; + + // Do not escape \""" or \''' + + for (size_t i = 0; i < str.size(); i++) { + if (str[i] == '\\') { + if (i + 3 < str.size()) { + if ((str[i + 1] == '\'') && (str[i + 2] == '\'') && + (str[i + 3] == '\'')) { + s += "\\'''"; + i += 3; + } else if ((str[i + 1] == '"') && (str[i + 2] == '"') && + (str[i + 3] == '"')) { + s += "\\\"\"\""; + i += 3; + } else { + s += "\\\\"; + } + } else { + s += "\\\\"; + } + } else { + s += str[i]; + } + } + + return s; + } else { + const std::string bs = "\\"; + const std::string bs_escaped = "\\\\"; + + std::string s = str; + + std::string::size_type pos = 0; + while ((pos = s.find(bs, pos)) != std::string::npos) { + s.replace(pos, bs.length(), bs_escaped); + pos += bs_escaped.length(); + } + + return s; + } +} + +std::string unescapeBackslash(const std::string &str) { + std::string s = str; + + std::string bs = "\\\\"; + std::string bs_unescaped = "\\"; + + std::string::size_type pos = 0; + while ((pos = s.find(bs, pos)) != std::string::npos) { + s.replace(pos, bs.length(), bs_unescaped); + pos += bs_unescaped.length(); + } + + return s; +} + +bool tokenize_variantElement(const std::string &elementName, + std::array *result) { + std::vector toks; + + // Ensure ElementPath is quoted with '{' and '}' + if (startsWith(elementName, "{") && endsWith(elementName, "}")) { + // ok + } else { + return false; + } + + // Remove variant quotation + std::string name = unwrap(elementName, "{", "}"); + + toks = split(name, "="); + if (toks.size() == 1) { + if (result) { + // ensure '=' and newline does not exist. + if (counts(toks[0], '=') || hasNewline(toks[0])) { + return false; + } + + (*result)[0] = toks[0]; + (*result)[1] = std::string(); + } + return true; + } else if (toks.size() == 2) { + if (result) { + // ensure '=' and newline does not exist. + if (counts(toks[0], '=') || hasNewline(toks[0])) { + return false; + } + + if (counts(toks[1], '=') || hasNewline(toks[1])) { + return false; + } + + (*result)[0] = toks[0]; + (*result)[1] = toks[1]; + } + return true; + } else { + return false; + } +} + +bool is_variantElementName(const std::string &name) { + return tokenize_variantElement(name); +} + +/// +/// Simply add number suffix to make unique string. +/// +/// - plane -> plane1 +/// - sphere1 -> sphere11 +/// - xform4 -> xform41 +/// +/// +bool makeUniqueName(std::multiset &nameSet, + const std::string &name, std::string *unique_name) { + if (!unique_name) { + return false; + } + + if (nameSet.count(name) == 0) { + (*unique_name) = name; + return 0; + } + + // Simply add number + + const size_t kMaxLoop = 1024; // to avoid infinite loop. + + std::string new_name = name; + + size_t cnt = 0; + while (cnt < kMaxLoop) { + size_t i = nameSet.count(new_name); + if (i == 0) { + // This should not happen though. + return false; + } + + new_name += std::to_string(i); + + if (nameSet.count(new_name) == 0) { + (*unique_name) = new_name; + return true; + } + + cnt++; + } + + return false; +} + +namespace detail { + +inline uint32_t utf8_len(const unsigned char c) { + if (c <= 127) { + // ascii + return 1; + } else if ((c & 0xE0) == 0xC0) { + return 2; + } else if ((c & 0xF0) == 0xE0) { + return 3; + } else if ((c & 0xF8) == 0xF0) { + return 4; + } + + // invalid + return 0; +} + +inline std::string extract_utf8_char(const std::string &str, uint32_t start_i, + int &len) { + len = 0; + + if ((start_i + 1) > str.size()) { + len = 0; + return std::string(); + } + + unsigned char c = static_cast(str[start_i]); + + if (c <= 127) { + // ascii + len = 1; + return str.substr(start_i, 1); + } else if ((c & 0xE0) == 0xC0) { + if ((start_i + 2) > str.size()) { + len = 0; + return std::string(); + } + len = 2; + return str.substr(start_i, 2); + } else if ((c & 0xF0) == 0xE0) { + if ((start_i + 3) > str.size()) { + len = 0; + return std::string(); + } + len = 3; + return str.substr(start_i, 3); + } else if ((c & 0xF8) == 0xF0) { + if ((start_i + 4) > str.size()) { + len = 0; + return std::string(); + } + len = 4; + return str.substr(start_i, 4); + } else { + // invalid utf8 + len = 0; + return std::string(); + } +} + +inline uint32_t to_codepoint(const char *s, uint32_t &char_len) { + if (!s) { + char_len = 0; + return ~0u; + } + + char_len = detail::utf8_len(static_cast(s[0])); + if (char_len == 0) { + return ~0u; + } + + uint32_t code = 0; + if (char_len == 1) { + unsigned char s0 = static_cast(s[0]); + if (s0 > 0x7f) { + return ~0u; + } + code = uint32_t(s0) & 0x7f; + } else if (char_len == 2) { + // 11bit: 110y-yyyx 10xx-xxxx + unsigned char s0 = static_cast(s[0]); + unsigned char s1 = static_cast(s[1]); + + if (((s0 & 0xe0) == 0xc0) && ((s1 & 0xc0) == 0x80)) { + code = (uint32_t(s0 & 0x1f) << 6) | (s1 & 0x3f); + } else { + return ~0u; + } + } else if (char_len == 3) { + // 16bit: 1110-yyyy 10yx-xxxx 10xx-xxxx + unsigned char s0 = static_cast(s[0]); + unsigned char s1 = static_cast(s[1]); + unsigned char s2 = static_cast(s[2]); + if (((s0 & 0xf0) == 0xe0) && ((s1 & 0xc0) == 0x80) && + ((s2 & 0xc0) == 0x80)) { + code = + (uint32_t(s0 & 0xf) << 12) | (uint32_t(s1 & 0x3f) << 6) | (s2 & 0x3f); + } else { + return ~0u; + } + } else if (char_len == 4) { + // 21bit: 1111-0yyy 10yy-xxxx 10xx-xxxx 10xx-xxxx + unsigned char s0 = static_cast(s[0]); + unsigned char s1 = static_cast(s[1]); + unsigned char s2 = static_cast(s[2]); + unsigned char s3 = static_cast(s[3]); + if (((s0 & 0xf8) == 0xf0) && ((s1 & 0xc0) == 0x80) && + ((s2 & 0xc0) == 0x80) && ((s2 & 0xc0) == 0x80)) { + code = (uint32_t(s0 & 0x7) << 18) | (uint32_t(s1 & 0x3f) << 12) | + (uint32_t(s2 & 0x3f) << 6) | uint32_t(s3 & 0x3f); + } else { + return ~0u; + } + } else { + // ??? + char_len = 0; + return ~0u; + } + + return code; +} + +} // namespace detail + +std::vector to_utf8_chars(const std::string &str) { + std::vector utf8_chars; + size_t sz = str.size(); + + for (size_t i = 0; i <= sz;) { + int len = 0; + std::string s = detail::extract_utf8_char(str, uint32_t(i), len); + if (len == 0) { + // invalid char + return std::vector(); + } + + i += uint64_t(len); + utf8_chars.push_back(s); + } + + return utf8_chars; +} + +uint32_t to_utf8_code(const std::string &s) { + if (s.empty() || (s.size() > 4)) { + return ~0u; // invalid + } + + // TODO: endianness. + uint32_t code = 0; + if (s.size() == 1) { + unsigned char s0 = static_cast(s[0]); + if (s0 > 0x7f) { + return ~0u; + } + code = uint32_t(s0) & 0x7f; + } else if (s.size() == 2) { + // 11bit: 110y-yyyx 10xx-xxxx + unsigned char s0 = static_cast(s[0]); + unsigned char s1 = static_cast(s[1]); + + if (((s0 & 0xe0) == 0xc0) && ((s1 & 0xc0) == 0x80)) { + code = (uint32_t(s0 & 0x1f) << 6) | (s1 & 0x3f); + } else { + return ~0u; + } + } else if (s.size() == 3) { + // 16bit: 1110-yyyy 10yx-xxxx 10xx-xxxx + unsigned char s0 = static_cast(s[0]); + unsigned char s1 = static_cast(s[1]); + unsigned char s2 = static_cast(s[2]); + if (((s0 & 0xf0) == 0xe0) && ((s1 & 0xc0) == 0x80) && + ((s2 & 0xc0) == 0x80)) { + code = + (uint32_t(s0 & 0xf) << 12) | (uint32_t(s1 & 0x3f) << 6) | (s2 & 0x3f); + } else { + return ~0u; + } + } else { + // 21bit: 1111-0yyy 10yy-xxxx 10xx-xxxx 10xx-xxxx + unsigned char s0 = static_cast(s[0]); + unsigned char s1 = static_cast(s[1]); + unsigned char s2 = static_cast(s[2]); + unsigned char s3 = static_cast(s[3]); + if (((s0 & 0xf8) == 0xf0) && ((s1 & 0xc0) == 0x80) && + ((s2 & 0xc0) == 0x80) && ((s2 & 0xc0) == 0x80)) { + code = (uint32_t(s0 & 0x7) << 18) | (uint32_t(s1 & 0x3f) << 12) | + (uint32_t(s2 & 0x3f) << 6) | uint32_t(s3 & 0x3f); + } else { + return ~0u; + } + } + + return code; +} + + +#if 0 +std::string to_utf8_char(const uint32_t code) { + + if (code < 128) { + std::string s = static_cast(code); + return s; + } + // TODO + +} +#endif + +bool is_valid_utf8(const std::string &str) { + // TODO: Consider UTF-BOM? + for (size_t i = 0; i < str.size();) { + uint32_t len = detail::utf8_len(*reinterpret_cast(&str[i])); + if (len == 0) { + return false; + } + i += len; + } + return true; +} + +std::vector to_codepoints(const std::string &str) { + + std::vector cps; + + for (size_t i = 0; i < str.size(); ) { + uint32_t char_len; + uint32_t cp = detail::to_codepoint(str.c_str() + i, char_len); + + if ((cp > kMaxUTF8Codepoint) || (char_len == 0)) { + return std::vector(); + } + + cps.push_back(cp); + + i += char_len; + } + + return cps; +} + +bool is_valid_utf8_identifier(const std::string &str) { + // First convert to codepoint values. + std::vector codepoints = to_codepoints(str); + + if (codepoints.empty()) { + return false; + } + + // (XID_Start|_) (XID_Continue|_)+ + + if ((codepoints[0] != '_') && !unicode_xid::is_xid_start(codepoints[0])) { + return false; + } + + for (size_t i = 1; i < codepoints.size(); i++) { + if ((codepoints[i] != '_') && !unicode_xid::is_xid_continue(codepoints[i])) { + return false; + } + } + + return true; +} + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/str-util.hh b/contrib/tinyusdz/tinyusdz_repo/src/str-util.hh new file mode 100644 index 000000000..35abc7f70 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/str-util.hh @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: MIT +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tinyusdz { + +constexpr size_t kMaxUTF8Codepoint = 0x10ffff; + +enum class CharEncoding +{ + None, + UTF8, + UTF8BOM, // UTF8 + BOM + UTF16LE // UTF16 LE(Windows Unicode) +}; + +inline const std::string to_lower(const std::string &str) { + std::string result = str; + std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { + if ((c >= 'A') && (c <= 'Z')) { + c += 32; + } + return static_cast(c); + }); + return result; +} + +inline bool hasNewline(const std::string &str) { + for (size_t i = 0; i < str.size(); i++) { + if ((str[i] == '\r') || (str[i] == '\n')) { + return true; + } + } + + return false; +} + +inline bool startsWith(const std::string &str, const std::string &t) { + return (str.size() >= t.size()) && + std::equal(std::begin(t), std::end(t), std::begin(str)); +} + +inline bool endsWith(const std::string &str, const std::string &suffix) { + return (str.size() >= suffix.size()) && + (str.find(suffix, str.size() - suffix.size()) != std::string::npos); +} + +inline std::string removePrefix(const std::string &str, + const std::string &prefix) { + if (startsWith(str, prefix)) { + return str.substr(prefix.length()); + } + return str; +} + +inline std::string removeSuffix(const std::string &str, + const std::string &suffix) { + if (endsWith(str, suffix)) { + return str.substr(0, str.length() - suffix.length()); + } + return str; +} + +inline bool contains(const std::string &str, char c) { + return str.find(c) != std::string::npos; +} + +inline bool contains_str(const std::string &str, const std::string &substr) { + return str.find(substr) != std::string::npos; +} + +inline size_t counts(const std::string &str, char c) { + size_t cnt = 0; + for (size_t i = 0; i < str.size(); i++) { + if (str[i] == c) { + cnt++; + } + } + return cnt; +} + +// Remove the beginning and the ending delimiter(s) from input string +// e.g. "mystring" -> mystring +// no error for an input string which does not contain `delim` in both side. +inline std::string unwrap(const std::string &str, + const std::string &delim = "\"") { + size_t n = delim.size(); + + if (str.size() < n) { + return str; + } + + std::string s = str; + + if (s.substr(0, n) == delim) { + s.erase(0, n); + } + + if (s.substr(s.size() - n) == delim) { + s.erase(s.size() - n); + } + + return s; +} + +inline std::string unwrap(const std::string &str, const std::string &l, + const std::string &r) { + return removePrefix(removeSuffix(str, r), l); +} + +inline std::string quote(const char *s, const std::string "e_str = "\"") { + return quote_str + std::string(s) + quote_str; +} + +inline std::string quote(const std::string &s, + const std::string "e_str = "\"") { + return quote_str + s + quote_str; +} + +inline std::string wquote(const std::string &s, + const std::string "e_lstr = "\"", + const std::string "e_rstr = "\"") { + return quote_lstr + s + quote_rstr; +} + +#if 0 +template +inline It quote(const It& v, const std::string "e_str = "\"") { + + It dst; + + for (typename It::const_iterator it = v.begin(); it != v.end(); ++it) { + dst.emplace_back(quote((*it), quote_str)); + } + + return dst; +} +#else +inline std::vector quote(const std::vector &vs, + const std::string "e_str = "\"") { + std::vector dst; + + for (const auto &item : vs) { + dst.emplace_back(quote(item, quote_str)); + } + + return dst; +} +#endif + +// Python like join ", ".join(v) +template +inline std::string join(const std::string &sep, const It &v) { + std::ostringstream oss; + if (!v.empty()) { + typename It::const_iterator it = v.begin(); + oss << *it++; + for (typename It::const_iterator e = v.end(); it != e; ++it) + oss << sep << *it; + } + return oss.str(); +} + +// To avoid splitting toooo large input text(e.g. few GB). +inline std::vector split( + const std::string &str, const std::string &sep, + const uint32_t kMaxItems = (std::numeric_limits::max)() / 100) { + size_t s; + size_t e = 0; + + size_t count = 0; + std::vector result; + + while ((s = str.find_first_not_of(sep, e)) != std::string::npos) { + e = str.find(sep, s); + result.push_back(str.substr(s, e - s)); + if (++count > kMaxItems) { + break; + } + } + + return result; +} + +// +// "{name=varname}" +// +// => ["name", "varname"] +// +// "{name=}" +// +// => ["name", ""] +// +// Return false when input string is not a variant element, or `name` and `varname` contains invalid characters. +// +bool tokenize_variantElement(const std::string &elementName, std::array *result = nullptr); + +bool is_variantElementName(const std::string &elementName); + +/// +/// Test if str contains " or ' +/// +/// @param[in] is_double_quote true: find escaped triple double quotes. false find escaped single double quotes. +/// +bool hasQuotes(const std::string &str, bool is_double_quote); + +/// +/// Test if str contains """ or ''' +/// +/// @param[in] is_double_quote true: find escaped triple double quotes. false find escaped single double quotes. +/// +bool hasTripleQuotes(const std::string &str, bool is_double_quote); + +/// +/// Test if str contains \""" or \''' +/// +/// @param[in] is_double_quote true: find escaped triple double quotes. false find escaped single double quotes. +/// @param[out] n The number of escaped triple quotes +/// +/// Return true immediately when an escaped triple quotes found when `n` is nullptr. +/// +bool hasEscapedTripleQuotes(const std::string &str, bool is_double_quote, size_t *n = nullptr); + +std::string escapeSingleQuote(const std::string &str, const bool is_double_quote); + +std::string escapeBackslash(const std::string &str, const bool triple_quoted_string = false); + +// Unescape backslash('\\' -> '\') +std::string unescapeBackslash(const std::string &str); + +std::string escapeControlSequence(const std::string &str); + +std::string unescapeControlSequence(const std::string &str); + +std::string buildEscapedAndQuotedStringForUSDA(const std::string &str); + +/// +/// Determine if input UTF-8 string is Unicode Identifier +/// (UAX31 Default Identifier) +/// +bool is_valid_utf8_identifier(const std::string &str); + +// TfIsValidIdentifier in pxrUSD equivalanet +// Supports UTF-8 identifier(UAX31 Default Identifier. pxrUSD supports UTF8 Identififer from 24.03) +inline bool isValidIdentifier(const std::string &str, bool is_utf8 = true) { + + if (str.empty()) { + return false; + } + + if (is_utf8) { + return is_valid_utf8_identifier(str); + } else { + // legacy + + // first char + // [a-ZA-Z_] + if ((('a' <= str[0]) && (str[0] <= 'z')) || (('A' <= str[0]) && (str[0] <= 'Z')) || (str[0] == '_')) { + // ok + } else { + return false; + } + + // remaining chars + // [a-ZA-Z0-9_] + for (size_t i = 1; i < str.length(); i++) { + if ((('a' <= str[i]) && (str[i] <= 'z')) || (('A' <= str[i]) && (str[i] <= 'Z')) || (('0' <= str[i]) && (str[i] <= '9')) || (str[i] == '_')) { + // ok + } else { + return false; + } + } + } + + return true; +} + + +// TfMakeValidIdentifier in pxrUSD equivalanet +// TODO: support UTF-8 +inline std::string makeIdentifierValid(const std::string &str, bool is_utf8 = true) { + (void)is_utf8; + + std::string s; + + if (str.empty()) { + // return '_' + return "_"; + } + + // first char + // [a-ZA-Z_] + if ((('a' <= str[0]) && (str[0] <= 'z')) || (('A' <= str[0]) && (str[0] <= 'Z')) || (str[0] == '_')) { + s.push_back(str[0]); + } else { + s.push_back('_'); + } + + // remain chars + // [a-ZA-Z0-9_] + for (size_t i = 1; i < str.length(); i++) { + if ((('a' <= str[i]) && (str[i] <= 'z')) || (('A' <= str[i]) && (str[i] <= 'Z')) || (('0' <= str[i]) && (str[i] <= '9')) || (str[i] == '_')) { + s.push_back(str[i]); + } else { + s.push_back('_'); + } + } + + return s; +} + +/// +/// Simply add number suffix to make unique string. +/// +/// - plane -> plane1 +/// - sphere1 -> sphere11 +/// - xform4 -> xform41 +/// +/// +bool makeUniqueName(std::multiset &nameSet, const std::string &name, std::string *unique_name); + + +/// +/// Determine if input string is valid UTF-8 string. +/// +bool is_valid_utf8(const std::string &str); + + +/// +/// Convert string buffer to list of UTF-8 chars. +/// Example: 'こんにちは' => ['こ', 'ん', 'に', 'ち', 'は'] +/// +std::vector to_utf8_chars(const std::string &str); + +/// +/// Convert UTF-8 char to codepoint. +/// Return ~0u(0xffffffff) when input `u8char` is not a valid UTF-8 charcter. +/// +uint32_t to_utf8_code(const std::string &u8char); + +/// +/// Convert UTF-8 string to codepoint values. +/// +/// Return empty array when input is not a valid UTF-8 string. +/// +std::vector to_codepoints(const std::string &str); + +/// +/// Convert UTF-8 codepoint to UTF-8 string. +/// +inline std::string codepoint_to_utf8(uint32_t code) { + if (code <= 0x7f) { + return std::string(1, char(code)); + } else if (code <= 0x7ff) { + // 11bit: 110y-yyyx 10xx-xxxx + uint8_t buf[2]; + buf[0] = uint8_t(((code >> 6) & 0x1f) | 0xc0); + buf[1] = uint8_t(((code >> 0) & 0x3f) | 0x80); + return std::string(reinterpret_cast(&buf[0]), 2); + } else if (code <= 0xffff) { + // 16bit: 1110-yyyy 10yx-xxxx 10xx-xxxx + uint8_t buf[3]; + buf[0] = uint8_t(((code >> 12) & 0x0f) | 0xe0); + buf[1] = uint8_t(((code >> 6) & 0x3f) | 0x80); + buf[2] = uint8_t(((code >> 0) & 0x3f) | 0x80); + return std::string(reinterpret_cast(&buf[0]), 3); + } else if (code <= 0x10ffff) { + // 21bit: 1111-0yyy 10yy-xxxx 10xx-xxxx 10xx-xxxx + uint8_t buf[4]; + buf[0] = uint8_t(((code >> 18) & 0x07) | 0xF0); + buf[1] = uint8_t(((code >> 12) & 0x3F) | 0x80); + buf[2] = uint8_t(((code >> 6) & 0x3F) | 0x80); + buf[3] = uint8_t(((code >> 0) & 0x3F) | 0x80); + return std::string(reinterpret_cast(&buf[0]), 4); + } + + // invalid + return std::string(); +} + + +#if 0 // TODO +/// +/// Convert UTF-8 code to UTF-8 char +/// +/// Return empty string when input `code` is not a valid UTF-8 code. +std::string to_utf8_char(const uint32_t code); +#endif + +#if 0 +template +inline std::string quote_then_join(const std::string& sep, const It& v, const std::string "e = "\"") +{ + std::ostringstream oss; + if (!v.empty()) { + typename It::const_iterator it = v.begin(); + oss << wrap(*it++; + for (typename It::const_iterator e = v.end(); it != e; ++it) + oss << sep << *it; + } + return oss.str(); +} +#endif + +#if 0 +template +inline std::string join(const std::string& sep, It& v) +{ + std::ostringstream oss; + if (!v.empty()) { + typename It::iterator it = v.begin(); + oss << *it++; + for (typename It::iterator e = v.end(); it != e; ++it) + oss << sep << *it; + } + return oss.str(); +} +#endif + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/stream-reader.hh b/contrib/tinyusdz/tinyusdz_repo/src/stream-reader.hh new file mode 100644 index 000000000..578366f86 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/stream-reader.hh @@ -0,0 +1,376 @@ +/* +Copyright (c) 2019 - 2020, Syoyo Fujita. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Syoyo Fujita nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +// +// Simple byte stream reader. Consider endianness when reading 2, 4, 8 bytes data. +// + +#include +#include +#include +#include + +namespace tinyusdz { + +namespace { + +static inline void swap2(unsigned short *val) { + unsigned short tmp = *val; + uint8_t *dst = reinterpret_cast(val); + uint8_t *src = reinterpret_cast(&tmp); + + dst[0] = src[1]; + dst[1] = src[0]; +} + +static inline void swap4(uint32_t *val) { + uint32_t tmp = *val; + uint8_t *dst = reinterpret_cast(val); + uint8_t *src = reinterpret_cast(&tmp); + + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; +} + +static inline void swap4(int *val) { + int tmp = *val; + uint8_t *dst = reinterpret_cast(val); + uint8_t *src = reinterpret_cast(&tmp); + + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; +} + +static inline void swap8(uint64_t *val) { + uint64_t tmp = (*val); + uint8_t *dst = reinterpret_cast(val); + uint8_t *src = reinterpret_cast(&tmp); + + dst[0] = src[7]; + dst[1] = src[6]; + dst[2] = src[5]; + dst[3] = src[4]; + dst[4] = src[3]; + dst[5] = src[2]; + dst[6] = src[1]; + dst[7] = src[0]; +} + +static inline void swap8(int64_t *val) { + int64_t tmp = (*val); + uint8_t *dst = reinterpret_cast(val); + uint8_t *src = reinterpret_cast(&tmp); + + dst[0] = src[7]; + dst[1] = src[6]; + dst[2] = src[5]; + dst[3] = src[4]; + dst[4] = src[3]; + dst[5] = src[2]; + dst[6] = src[1]; + dst[7] = src[0]; +} + +} // namespace + +/// +/// Simple stream reader +/// +class StreamReader { + public: + explicit StreamReader(const uint8_t *binary, const uint64_t length, + const bool swap_endian) + : binary_(binary), length_(length), swap_endian_(swap_endian), idx_(0) { + (void)pad_; + } + + bool seek_set(const uint64_t offset) const { + if (offset > length_) { + return false; + } + + idx_ = offset; + return true; + } + + bool seek_from_current(const int64_t offset) const { + if ((int64_t(idx_) + offset) < 0) { + return false; + } + + if (uint64_t((int64_t(idx_) + offset)) > length_) { + return false; + } + + idx_ = uint64_t(int64_t(idx_) + offset); + return true; + } + + uint64_t read(const uint64_t n, const uint64_t dst_len, uint8_t *dst) const { + uint64_t len = n; + if ((idx_ + len) > length_) { + len = length_ - uint64_t(idx_); + } + + if (len > 0) { + if (dst_len < len) { + // dst does not have enough space. return 0 for a while. + return 0; + } + + size_t nbytes = size_t(len); // may shorten size on 32bit platform + + memcpy(dst, &binary_[idx_], nbytes); + idx_ += nbytes; + return nbytes; + + } else { + return 0; + } + } + + bool read1(uint8_t *ret) const { + if ((idx_ + 1) > length_) { + return false; + } + + const uint8_t val = binary_[idx_]; + + (*ret) = val; + idx_ += 1; + + return true; + } + + bool read_bool(bool *ret) const { + if ((idx_ + 1) > length_) { + return false; + } + + const char val = static_cast(binary_[idx_]); + + (*ret) = bool(val); + idx_ += 1; + + return true; + } + + bool read1(char *ret) const { + if ((idx_ + 1) > length_) { + return false; + } + + const char val = static_cast(binary_[idx_]); + + (*ret) = val; + idx_ += 1; + + return true; + } + + bool read2(unsigned short *ret) const { + if ((idx_ + 2) > length_) { + return false; + } + + unsigned short val; + memcpy(&val, &binary_[idx_], sizeof(val)); + + if (swap_endian_) { + swap2(&val); + } + + (*ret) = val; + idx_ += 2; + + return true; + } + + bool read4(uint32_t *ret) const { + if ((idx_ + 4) > length_) { + return false; + } + + uint32_t val; + memcpy(&val, &binary_[idx_], sizeof(val)); + + if (swap_endian_) { + swap4(&val); + } + + (*ret) = val; + idx_ += 4; + + return true; + } + + bool read4(int *ret) const { + if ((idx_ + 4) > length_) { + return false; + } + + int val; + memcpy(&val, &binary_[idx_], sizeof(val)); + + if (swap_endian_) { + swap4(&val); + } + + (*ret) = val; + idx_ += 4; + + return true; + } + + bool read8(uint64_t *ret) const { + if ((idx_ + 8) > length_) { + return false; + } + + uint64_t val; + memcpy(&val, &binary_[idx_], sizeof(val)); + + if (swap_endian_) { + swap8(&val); + } + + (*ret) = val; + idx_ += 8; + + return true; + } + + bool read8(int64_t *ret) const { + if ((idx_ + 8) > length_) { + return false; + } + + int64_t val; + memcpy(&val, &binary_[idx_], sizeof(val)); + + if (swap_endian_) { + swap8(&val); + } + + (*ret) = val; + idx_ += 8; + + return true; + } + + bool read_float(float *ret) const { + if (!ret) { + return false; + } + + float value; + if (!read4(reinterpret_cast(&value))) { + return false; + } + + (*ret) = value; + + return true; + } + + bool read_double(double *ret) const { + if (!ret) { + return false; + } + + double value; + if (!read8(reinterpret_cast(&value))) { + return false; + } + + (*ret) = value; + + return true; + } + +#if 0 + bool read_value(Value *inout) { + if (!inout) { + return false; + } + + if (inout->Type() == VALUE_TYPE_FLOAT) { + float value; + if (!read_float(&value)) { + return false; + } + + (*inout) = Value(value); + } else if (inout->Type() == VALUE_TYPE_INT) { + int value; + if (!read4(&value)) { + return false; + } + + (*inout) = Value(value); + } else { + TINYVDBIO_ASSERT(0); + return false; + } + + return true; + } +#endif + + uint64_t tell() const { return uint64_t(idx_); } + bool eof() const { return idx_ >= length_; } + + bool is_nullchar() const { + if (idx_ < length_) { + return binary_[idx_] == '\0'; + } + + // TODO: report true when eof()? + return false; + } + + const uint8_t *data() const { return binary_; } + + bool swap_endian() const { return swap_endian_; } + + uint64_t size() const { return length_; } + + private: + const uint8_t *binary_; + const uint64_t length_; + bool swap_endian_; + char pad_[7]; + mutable uint64_t idx_; +}; + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/stream-writer.hh b/contrib/tinyusdz/tinyusdz_repo/src/stream-writer.hh new file mode 100644 index 000000000..010a486a1 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/stream-writer.hh @@ -0,0 +1,335 @@ +/* +Copyright (c) 2022 - Present Syoyo Fujita. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Syoyo Fujita nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +// +// Simple byte stream writer. Consider endianness when writing 2, 4, 8 bytes data. +// + +#include +#include +#include +#include + +namespace tinyusdz { + +namespace { + +static inline void swap2(unsigned short *val) { + unsigned short tmp = *val; + uint8_t *dst = reinterpret_cast(val); + uint8_t *src = reinterpret_cast(&tmp); + + dst[0] = src[1]; + dst[1] = src[0]; +} + +static inline void swap4(uint32_t *val) { + uint32_t tmp = *val; + uint8_t *dst = reinterpret_cast(val); + uint8_t *src = reinterpret_cast(&tmp); + + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; +} + +static inline void swap4(int *val) { + int tmp = *val; + uint8_t *dst = reinterpret_cast(val); + uint8_t *src = reinterpret_cast(&tmp); + + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; +} + +static inline void swap8(uint64_t *val) { + uint64_t tmp = (*val); + uint8_t *dst = reinterpret_cast(val); + uint8_t *src = reinterpret_cast(&tmp); + + dst[0] = src[7]; + dst[1] = src[6]; + dst[2] = src[5]; + dst[3] = src[4]; + dst[4] = src[3]; + dst[5] = src[2]; + dst[6] = src[1]; + dst[7] = src[0]; +} + +static inline void swap8(int64_t *val) { + int64_t tmp = (*val); + uint8_t *dst = reinterpret_cast(val); + uint8_t *src = reinterpret_cast(&tmp); + + dst[0] = src[7]; + dst[1] = src[6]; + dst[2] = src[5]; + dst[3] = src[4]; + dst[4] = src[3]; + dst[5] = src[2]; + dst[6] = src[1]; + dst[7] = src[0]; +} + +} // namespace + +#if 0 // TODO + +/// +/// Simple stream writeer +/// +class StreamWriter { + public: + // max_length: Max byte lengths. + explicit StreamWriter(const size_t max_length, + const bool swap_endian) + : max_length_(max_length), swap_endian_(swap_endian), idx_(0) { + (void)pad_; + } + + bool seek_set(const uint64_t offset) const { + if (offset >= max_length_) { + return false; + } + + idx_ = offset; + return true; + } + + bool seek_from_current(const int64_t offset) const { + if ((int64_t(idx_) + offset) < 0) { + return false; + } + + if (size_t((int64_t(idx_) + offset)) > length_) { + return false; + } + + idx_ = size_t(int64_t(idx_) + offset); + return true; + } + + size_t writeN(const size_t n, const uint64_t dst_len, uint8_t *dst) const { + size_t len = n; + if ((idx_ + len) > length_) { + len = length_ - size_t(idx_); + } + + if (len > 0) { + if (dst_len < len) { + // dst does not have enough space. return 0 for a while. + return 0; + } + + memcpy(dst, &binary_[idx_], len); + idx_ += len; + return len; + + } else { + return 0; + } + } + + bool write1(uint8_t *ret) const { + if ((idx_ + 1) > length_) { + return false; + } + + const uint8_t val = binary_[idx_]; + + (*ret) = val; + idx_ += 1; + + return true; + } + + bool write_bool(bool *ret) const { + if ((idx_ + 1) > length_) { + return false; + } + + const char val = static_cast(binary_[idx_]); + + (*ret) = bool(val); + idx_ += 1; + + return true; + } + + bool write1(char *ret) const { + if ((idx_ + 1) > length_) { + return false; + } + + const char val = static_cast(binary_[idx_]); + + (*ret) = val; + idx_ += 1; + + return true; + } + + bool write2(unsigned short *ret) const { + if ((idx_ + 2) > length_) { + return false; + } + + unsigned short val = + *(reinterpret_cast(&binary_[idx_])); + + if (swap_endian_) { + swap2(&val); + } + + (*ret) = val; + idx_ += 2; + + return true; + } + + bool write4(uint32_t *ret) const { + if ((idx_ + 4) > length_) { + return false; + } + + uint32_t val = *(reinterpret_cast(&binary_[idx_])); + + if (swap_endian_) { + swap4(&val); + } + + (*ret) = val; + idx_ += 4; + + return true; + } + + bool write4(int *ret) const { + if ((idx_ + 4) > length_) { + return false; + } + + int val = *(reinterpret_cast(&binary_[idx_])); + + if (swap_endian_) { + swap4(&val); + } + + (*ret) = val; + idx_ += 4; + + return true; + } + + bool write8(uint64_t *ret) const { + if ((idx_ + 8) > length_) { + return false; + } + + uint64_t val = *(reinterpret_cast(&binary_[idx_])); + + if (swap_endian_) { + swap8(&val); + } + + (*ret) = val; + idx_ += 8; + + return true; + } + + bool write8(int64_t *ret) const { + if ((idx_ + 8) > length_) { + return false; + } + + int64_t val = *(reinterpret_cast(&binary_[idx_])); + + if (swap_endian_) { + swap8(&val); + } + + (*ret) = val; + idx_ += 8; + + return true; + } + + bool write_float(const float value) const { + if (!write4(reinterpret_cast(&value))) { + return false; + } + + return true; + } + + bool write_double(const double value) const { + if (!write8(reinterpret_cast(&value))) { + return false; + } + + return true; + } + + size_t tell() const { return size_t(idx_); } + //bool eof() const { return idx_ >= length_; } + + bool swap_endian() const { return swap_endian_; } + + size_t size() const { return length_; } + + private: + + bool Reserve_(size_t additional_bytes) { + size_t req_bytes = binary_.size() + additional_bytes; + + if (req_bytes > max_length_) { + return false; + } + + // grow +20% + + // + binary_.resize + + } + + const std::vector binary_; + const size_t max_length_; + bool swap_endian_; + char pad_[7]; + mutable uint64_t idx_; +}; +#endif + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/subdiv.cc b/contrib/tinyusdz/tinyusdz_repo/src/subdiv.cc new file mode 100644 index 000000000..baf4e461a --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/subdiv.cc @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2020 - 2023 Syoyo Fujita. +// Copyright 2023 - Present Light Transport Entertainment Inc. + +#ifdef _MSC_VER +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES +#endif +#endif + +#include +#include +#include +#include + +#include "subdiv.hh" + +#include "common-macros.inc" + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +using namespace OpenSubdiv; + +namespace tinyusdz { + +//------------------------------------------------------------------------------ +// Face-varying container implementation. +// +// We are using a uv texture layout as a 'face-varying' primitive variable +// attribute. Because face-varying data is specified 'per-face-per-vertex', +// we cannot use the same container that we use for 'vertex' or 'varying' +// data. We specify a new container, which only carries (u,v) coordinates. +// Similarly to our 'Vertex' container, we add a minimalistic interpolation +// interface with a 'Clear()' and 'AddWithWeight()' methods. +// +struct FVarVertexUV { + // Minimal required interface ---------------------- + void Clear() { u = v = 0.0f; } + + void AddWithWeight(FVarVertexUV const &src, float weight) { + u += weight * src.u; + v += weight * src.v; + } + + // Basic 'uv' layout channel + float u, v; +}; + +struct FVarVertexColor { + // Minimal required interface ---------------------- + void Clear() { r = g = b = a = 0.0f; } + + void AddWithWeight(FVarVertexColor const &src, float weight) { + r += weight * src.r; + g += weight * src.g; + b += weight * src.b; + a += weight * src.a; + } + + // Basic 'color' layout channel + float r, g, b, a; +}; + +bool subdivide(int subd_level, const ControlQuadMesh &in_mesh, SubdividedMesh *out_mesh, + std::string *err, + bool dump) { + if (subd_level < 0) { + subd_level = 0; + } + + DCOUT("SubD: level = " << subd_level); + + const auto start_t = std::chrono::system_clock::now(); + + int maxlevel = subd_level; + + if (maxlevel > 8) { + maxlevel = 8; + DCOUT("SubD: limit subd level to " << maxlevel); + } + + typedef Far::TopologyDescriptor Descriptor; + + Sdc::SchemeType type = OpenSubdiv::Sdc::SCHEME_CATMARK; + + Sdc::Options options; + options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_ONLY); + options.SetFVarLinearInterpolation(Sdc::Options::FVAR_LINEAR_NONE); + + // Populate a topology descriptor with our raw data + Descriptor desc; + desc.numVertices = int(in_mesh.vertices.size() / 3); + desc.numFaces = int(in_mesh.verts_per_faces.size()); + desc.numVertsPerFace = in_mesh.verts_per_faces.data(); + desc.vertIndicesPerFace = in_mesh.indices.data(); + + // TODO(syoyo): Support various vertex attributes. + +#if 0 // TODO + int channelUV = 0; + int channelColor = 1; + + // Create a face-varying channel descriptor + Descriptor::FVarChannel channels[2]; + channels[channelUV].numValues = g_nuvs; + channels[channelUV].valueIndices = g_uvIndices; + channels[channelColor].numValues = g_ncolors; + channels[channelColor].valueIndices = g_colorIndices; + + // Add the channel topology to the main descriptor + desc.numFVarChannels = 2; + desc.fvarChannels = channels; +#else + desc.numFVarChannels = 0; +#endif + + // Instantiate a Far::TopologyRefiner from the descriptor + Far::TopologyRefiner *refiner = + Far::TopologyRefinerFactory::Create( + desc, + Far::TopologyRefinerFactory::Options(type, options)); + + // Uniformly refine the topology up to 'maxlevel' + // note: fullTopologyInLastLevel must be true to work with face-varying data + { + Far::TopologyRefiner::UniformOptions refineOptions(maxlevel); + refineOptions.fullTopologyInLastLevel = true; + refiner->RefineUniform(refineOptions); + } + + // Allocate and initialize the 'vertex' primvar data (see tutorial 2 for + // more details). + std::vector vbuffer(size_t(refiner->GetNumVerticesTotal())); + Vertex *verts = &vbuffer[0]; + + for (size_t i = 0; i < in_mesh.vertices.size() / 3; ++i) { + verts[i].SetPosition(in_mesh.vertices[3 * i + 0], + in_mesh.vertices[3 * i + 1], + in_mesh.vertices[3 * i + 2]); + } + +#if 0 + // Allocate and initialize the first channel of 'face-varying' primvar data (UVs) + std::vector fvBufferUV(refiner->GetNumFVarValuesTotal(channelUV)); + FVarVertexUV * fvVertsUV = &fvBufferUV[0]; + for (int i=0; i fvBufferColor(refiner->GetNumFVarValuesTotal(channelColor)); + FVarVertexColor * fvVertsColor = &fvBufferColor[0]; + for (int i=0; iGetLevel(level - 1).GetNumVertices(); + // FVarVertexUV * dstFVarUV = srcFVarUV + + // refiner->GetLevel(level-1).GetNumFVarValues(channelUV); FVarVertexColor * + // dstFVarColor = srcFVarColor + + // refiner->GetLevel(level-1).GetNumFVarValues(channelColor); + + primvarRefiner.Interpolate(level, srcVert, dstVert); + // primvarRefiner.InterpolateFaceVarying(level, srcFVarUV, dstFVarUV, + // channelUV); primvarRefiner.InterpolateFaceVarying(level, srcFVarColor, + // dstFVarColor, channelColor); + + srcVert = dstVert; + // srcFVarUV = dstFVarUV; + // srcFVarColor = dstFVarColor; + } + + { // Output + + std::ofstream ofs; + if (dump) { + ofs.open("subd.obj"); + } + + Far::TopologyLevel const &refLastLevel = refiner->GetLevel(maxlevel); + + int nverts = refLastLevel.GetNumVertices(); + // int nuvs = refLastLevel.GetNumFVarValues(channelUV); + // int ncolors= refLastLevel.GetNumFVarValues(channelColor); + int nfaces = refLastLevel.GetNumFaces(); + + DCOUT("nverts = " << nverts << ", nfaces = " << nfaces); + + // Print vertex positions + int firstOfLastVerts = refiner->GetNumVerticesTotal() - nverts; + + out_mesh->vertices.resize(size_t(nverts) * 3); + + for (size_t vert = 0; vert < size_t(nverts); ++vert) { + float const *pos = verts[size_t(firstOfLastVerts) + vert].GetPosition(); + ofs << "v " << pos[0] << " " << pos[1] << " " << pos[2] << "\n"; + out_mesh->vertices[3 * vert + 0] = pos[0]; + out_mesh->vertices[3 * vert + 1] = pos[1]; + out_mesh->vertices[3 * vert + 2] = pos[2]; + } + +#if 0 + // Print uvs + int firstOfLastUvs = refiner->GetNumFVarValuesTotal(channelUV) - nuvs; + + for (int fvvert = 0; fvvert < nuvs; ++fvvert) { + FVarVertexUV const & uv = fvVertsUV[firstOfLastUvs + fvvert]; + printf("vt %f %f\n", uv.u, uv.v); + } + + // Print colors + int firstOfLastColors = refiner->GetNumFVarValuesTotal(channelColor) - ncolors; + + for (int fvvert = 0; fvvert < nuvs; ++fvvert) { + FVarVertexColor const & c = fvVertsColor[firstOfLastColors + fvvert]; + printf("c %f %f %f %f\n", c.r, c.g, c.b, c.a); + } +#endif + + out_mesh->triangulated_indices.clear(); + out_mesh->face_num_verts.clear(); + out_mesh->face_index_offsets.clear(); + out_mesh->face_ids.clear(); + out_mesh->face_triangle_ids.clear(); + out_mesh->material_ids.clear(); + + for (int face = 0; face < nfaces; ++face) { + Far::ConstIndexArray fverts = refLastLevel.GetFaceVertices(face); + // Far::ConstIndexArray fuvs = refLastLevel.GetFaceFVarValues(face, + // channelUV); + + // all refined Catmark faces should be quads + // assert(fverts.size()==4 && fuvs.size()==4); + if (fverts.size() != 4) { + if (err) { + (*err) += "All refined Catmark faces should be quads.\n"; + } + return false; + } + + out_mesh->face_index_offsets.push_back(uint32_t(out_mesh->face_num_verts.size())); + + out_mesh->face_num_verts.push_back(uint8_t(fverts.size())); + + if (dump) { + ofs << "f"; + } + for (int vert = 0; vert < fverts.size(); ++vert) { + out_mesh->face_indices.push_back(uint8_t(fverts[vert])); + + if (dump) { + // OBJ uses 1-based arrays... + ofs << " " << fverts[vert] + 1; + } + } + + if (dump) { + ofs << "\n"; + } + + // triangulated face + out_mesh->triangulated_indices.push_back(uint8_t(fverts[0])); + out_mesh->triangulated_indices.push_back(uint8_t(fverts[1])); + out_mesh->triangulated_indices.push_back(uint8_t(fverts[2])); + + out_mesh->triangulated_indices.push_back(uint8_t(fverts[2])); + out_mesh->triangulated_indices.push_back(uint8_t(fverts[3])); + out_mesh->triangulated_indices.push_back(uint8_t(fverts[0])); + + // some face attribs. + out_mesh->face_ids.push_back(uint32_t(face)); + out_mesh->face_ids.push_back(uint32_t(face)); + + out_mesh->face_triangle_ids.push_back(0); + out_mesh->face_triangle_ids.push_back(1); + + // -1 = no material + out_mesh->material_ids.push_back(-1); + out_mesh->material_ids.push_back(-1); + } + } + + const auto end_t = std::chrono::system_clock::now(); + const double elapsed = double( + std::chrono::duration_cast(end_t - start_t) + .count()); + + (void)elapsed; + DCOUT("SubD time : " << elapsed << " [ms]"); + + if (dump) { + std::cout << "dumped subdivided mesh as `subd.obj`\n"; + } + + return true; +} + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/subdiv.hh b/contrib/tinyusdz/tinyusdz_repo/src/subdiv.hh new file mode 100644 index 000000000..7b9aae290 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/subdiv.hh @@ -0,0 +1,109 @@ +#pragma once + +#include +#include + +namespace tinyusdz { + +/// +/// Subdivided Mesh(for rendering. trianglulated) +/// +typedef struct { + // num_triangle_faces = indices.size() / 3 + std::vector vertices; /// [xyz] * num_vertices + std::vector vertex_colors; /// [rgb] * num_vertices + + std::vector + facevarying_normals; /// [xyz] * 3(triangle) * num_triangle_faces + std::vector + facevarying_tangents; /// [xyz] * 3(triangle) * num_triangle_faces + std::vector + facevarying_binormals; /// [xyz] * 3(triangle) * num_triangle_faces + std::vector + facevarying_uvs; /// [xy] * 3(triangle) * num_triangle_faces + + std::vector material_ids; /// per face materials. -1 = no material. index x num_triangle_faces + + // List of triangle vertex indices. For NanoRT BVH + std::vector + triangulated_indices; /// 3(triangle) x num_triangle_faces + + // List of original vertex indices. For UV interpolation + std::vector + face_indices; /// length = sum(for each face_num_verts[i]) + + // Offset to `face_indices` for a given face_id. + std::vector face_index_offsets; /// length = face_num_verts.size() + + std::vector face_num_verts; /// # of vertices per face + + // face ID for each triangle. For ptex textureing. + std::vector face_ids; /// index x num_triangle_faces + + // Triangule ID of a face(e.g. 0 for triangle primitive. 0 or 1 for quad + // primitive(tessellated into two-triangles) + std::vector face_triangle_ids; /// index x num_triangle_faces + +} SubdividedMesh; + +/// +/// Initial control mesh(input to Subdivision Surface) +/// All faces should be quad face for this example program. +/// +struct ControlQuadMesh { + std::vector vertices; // [xyz] * num_vertices + std::vector indices; // length = sum_{i}(verts_per_face[i]) + std::vector verts_per_faces; // should be 4(quad) + + std::vector faevarying_uvs; // 2 * num_faces(vert_per_faces.size) + std::vector facevarying_uv_indices; // length = indices.size() +}; + +/// +/// template class for OSD. +/// +struct Vertex { + // Minimal required interface ---------------------- + Vertex() = default; + + Vertex(const Vertex &src) { + _position[0] = src._position[0]; + _position[1] = src._position[1]; + _position[2] = src._position[2]; + } + + Vertex &operator=(const Vertex &rhs) = default; + + void Clear(void * = nullptr) { _position[0] = _position[1] = _position[2] = 0.0f; } + + void AddWithWeight(Vertex const &src, float weight) { + _position[0] += weight * src._position[0]; + _position[1] += weight * src._position[1]; + _position[2] += weight * src._position[2]; + } + + // Public interface ------------------------------------ + void SetPosition(float x, float y, float z) { + _position[0] = x; + _position[1] = y; + _position[2] = z; + } + + const float *GetPosition() const { return _position; } + + private: + float _position[3]; +}; + +/// +/// Uniformly subdivide the mesh. +/// +/// @param[in] level Subdivision level. +/// @param[in] in_mesh Input quad mesh. +/// @param[out] out_mesh Subdivided mesh. +/// @param[in] dump Dump .obj for debugging. +/// +bool subdivide(int level, const ControlQuadMesh &in_mesh, + SubdividedMesh *out_mesh, std::string *err, bool dump = false); + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/texture-types.hh b/contrib/tinyusdz/tinyusdz_repo/src/texture-types.hh new file mode 100644 index 000000000..d64d0d3c9 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/texture-types.hh @@ -0,0 +1,68 @@ +/* +Copyright (c) 2022 - Present, Syoyo Fujita. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Syoyo Fujita nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#pragma once + +#include + +enum ColorSpace { + COLORSPACE_NONE, // No explicit colorspace + COLORSPACE_SRGB, + COLORSPACE_LINEAR +}; + +// +// Simple tile coordinate for UDIM texture +// +struct TileCoord { + int32_t x{0}; + int32_t y{0}; +}; + +struct Texture { + + uint32_t _image_id; // ID to `Image`. + + int32_t _width; + int32_t _height; + int32_t _stride; // width stride + int32_t _channels; + + ColorSpace colorspace{COLORSPACE_SRGB}; // Default = sRGB + +}; + +struct UDIMTexture { + std::vector> _textures; +}; + +// For CPU texture mapping +struct TextureSampler { + + bool Sample(const Texture &tex, float u, float v, float w); + +}; + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tiny-any.inc b/contrib/tinyusdz/tinyusdz_repo/src/tiny-any.inc new file mode 100644 index 000000000..62a9890a3 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tiny-any.inc @@ -0,0 +1,633 @@ +// +// Implementation of N4562 std::experimental::any (merged into C++17) for C++11 compilers. +// +// See also: +// + http://en.cppreference.com/w/cpp/any +// + http://en.cppreference.com/w/cpp/experimental/any +// + http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4562.html#any +// + https://cplusplus.github.io/LWG/lwg-active.html#2509 +// +// +// Copyright (c) 2016 Denilson das Mercês Amorim +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// TinyUSDZ modification +// - Disable exceptions, RTTI +// - Use type_id with TypeTraits::type_id +// - Use type_name with TypeTraits::type_name +// - Assume this tiny-any.inc is included inside value-type.hh (since TypeTraits implementations are required) +// +#ifndef LINB_ANY_HPP +#define LINB_ANY_HPP +#pragma once + +//#include +#include +//#include +#include +#include + +#if 0 +//#include "value-type.hh" + +namespace tinyusdz { +namespace value { + +// Forward decl. +template +class TypeTraits; + +} // namespace value +} // namespace tinyusdz +#endif + +#ifndef ANY_IMPL_NO_EXCEPTIONS +#define ANY_IMPL_NO_EXCEPTIONS +#endif + +#ifndef ANY_IMPL_NO_RTTI +#define ANY_IMPL_NO_RTTI +#endif + + +#if defined(PARTICLE) +#if !defined(__cpp_exceptions) && !defined(ANY_IMPL_NO_EXCEPTIONS) && !defined(ANY_IMPL_EXCEPTIONS) +# define ANY_IMPL_NO_EXCEPTIONS +# endif +#else +// you can opt-out of exceptions by definining ANY_IMPL_NO_EXCEPTIONS, +// but you must ensure not to cast badly when passing an `any' object to any_cast(any) +#endif + +#if defined(PARTICLE) +#if !defined(__cpp_rtti) && !defined(ANY_IMPL_NO_RTTI) && !defined(ANY_IMPL_RTTI) +# define ANY_IMPL_NO_RTTI +# endif +#else +// you can opt-out of RTTI by defining ANY_IMPL_NO_RTTI, +// in order to disable functions working with the typeid of a type +#endif + + +namespace linb +{ + + +#ifndef ANY_IMPL_NO_EXCEPTIONS +class bad_any_cast : public std::bad_cast +{ +public: + const char* what() const noexcept override + { + return "bad any cast"; + } +}; +#endif + +class any final +{ +public: + /// Constructs an object of type any with an empty state. + any() : + vtable(nullptr) + { + } + + /// Constructs an object of type any with an equivalent state as other. + any(const any& rhs) : + vtable(rhs.vtable) + { + if(!rhs.empty()) + { + rhs.vtable->copy(rhs.storage, this->storage); + } + } + + /// Constructs an object of type any with a state equivalent to the original state of other. + /// rhs is left in a valid but otherwise unspecified state. + any(any&& rhs) noexcept : + vtable(rhs.vtable) + { + if(!rhs.empty()) + { + rhs.vtable->move(rhs.storage, this->storage); + rhs.vtable = nullptr; + } + } + + /// Same effect as this->clear(). + ~any() + { + this->clear(); + } + + /// Constructs an object of type any that contains an object of type T direct-initialized with std::forward(value). + /// + /// T shall satisfy the CopyConstructible requirements, otherwise the program is ill-formed. + /// This is because an `any` may be copy constructed into another `any` at any time, so a copy should always be allowed. + template::type, any>::value>::type> + any(ValueType&& value) + { + static_assert(std::is_copy_constructible::type>::value, + "T shall satisfy the CopyConstructible requirements."); + this->construct(std::forward(value)); + } + + /// Has the same effect as any(rhs).swap(*this). No effects if an exception is thrown. + any& operator=(const any& rhs) + { + any(rhs).swap(*this); + return *this; + } + + /// Has the same effect as any(std::move(rhs)).swap(*this). + /// + /// The state of *this is equivalent to the original state of rhs and rhs is left in a valid + /// but otherwise unspecified state. + any& operator=(any&& rhs) noexcept + { + any(std::move(rhs)).swap(*this); + return *this; + } + + /// Has the same effect as any(std::forward(value)).swap(*this). No effect if a exception is thrown. + /// + /// T shall satisfy the CopyConstructible requirements, otherwise the program is ill-formed. + /// This is because an `any` may be copy constructed into another `any` at any time, so a copy should always be allowed. + template::type, any>::value>::type> + any& operator=(ValueType&& value) + { + static_assert(std::is_copy_constructible::type>::value, + "T shall satisfy the CopyConstructible requirements."); + any(std::forward(value)).swap(*this); + return *this; + } + + /// If not empty, destroys the contained object. + void clear() noexcept + { + if(!empty()) + { + this->vtable->destroy(storage); + this->vtable = nullptr; + } + } + + /// Returns true if *this has no contained object, otherwise false. + bool empty() const noexcept + { + return this->vtable == nullptr; + } + +#ifndef ANY_IMPL_NO_RTTI + /// If *this has a contained object of type T, typeid(T); otherwise typeid(void). + const std::type_info& type() const noexcept + { + return empty()? typeid(void) : this->vtable->type(); + } +#endif + +#if 1 // tinyusdz + uint32_t type_id() const noexcept + { + return empty()? tinyusdz::value::TypeTraits::type_id() : this->vtable->type_id(); + } + + uint32_t underlying_type_id() const noexcept + { + return empty()? tinyusdz::value::TypeTraits::underlying_type_id() : this->vtable->underlying_type_id(); + } + + const std::string type_name() const noexcept + { + return empty()? tinyusdz::value::TypeTraits::type_name() : this->vtable->type_name(); + } + + const std::string underlying_type_name() const noexcept + { + return empty()? tinyusdz::value::TypeTraits::underlying_type_name() : this->vtable->underlying_type_name(); + } +#endif + + /// Exchange the states of *this and rhs. + void swap(any& rhs) noexcept + { + if(this->vtable != rhs.vtable) + { + any tmp(std::move(rhs)); + + // move from *this to rhs. + rhs.vtable = this->vtable; + if(this->vtable != nullptr) + { + this->vtable->move(this->storage, rhs.storage); + //this->vtable = nullptr; -- unneeded, see below + } + + // move from tmp (previously rhs) to *this. + this->vtable = tmp.vtable; + if(tmp.vtable != nullptr) + { + tmp.vtable->move(tmp.storage, this->storage); + tmp.vtable = nullptr; + } + } + else // same types + { + if(this->vtable != nullptr) + this->vtable->swap(this->storage, rhs.storage); + } + } + + /// Casts (with no type_info checks) the storage pointer as const T*. + template + const T* cast() const noexcept + { + return requires_allocation::type>::value? + reinterpret_cast(storage.dynamic) : + reinterpret_cast(&storage.stack); + } + + /// Casts (with no type_info checks) the storage pointer as T*. + template + T* cast() noexcept + { + return requires_allocation::type>::value? + reinterpret_cast(storage.dynamic) : + reinterpret_cast(&storage.stack); + } + +private: // Storage and Virtual Method Table + + union storage_union + { + using stack_storage_t = typename std::aligned_storage<2 * sizeof(void*), std::alignment_of::value>::type; + + void* dynamic; + stack_storage_t stack; // 2 words for e.g. shared_ptr + }; + + /// Base VTable specification. + struct vtable_type + { + // Note: The caller is responssible for doing .vtable = nullptr after destructful operations + // such as destroy() and/or move(). + +#ifndef ANY_IMPL_NO_RTTI + /// The type of the object this vtable is for. + const std::type_info& (*type)() noexcept; +#endif + +#if 1 + uint32_t (*type_id)() noexcept; + uint32_t (*underlying_type_id)() noexcept; + const std::string (*type_name)() noexcept; + const std::string (*underlying_type_name)() noexcept; +#endif + + /// Destroys the object in the union. + /// The state of the union after this call is unspecified, caller must ensure not to use src anymore. + void(*destroy)(storage_union&) noexcept; + + /// Copies the **inner** content of the src union into the yet unitialized dest union. + /// As such, both inner objects will have the same state, but on separate memory locations. + void(*copy)(const storage_union& src, storage_union& dest); + + /// Moves the storage from src to the yet unitialized dest union. + /// The state of src after this call is unspecified, caller must ensure not to use src anymore. + void(*move)(storage_union& src, storage_union& dest) noexcept; + + /// Exchanges the storage between lhs and rhs. + void(*swap)(storage_union& lhs, storage_union& rhs) noexcept; + }; + + /// VTable for dynamically allocated storage. + template + struct vtable_dynamic + { +#ifndef ANY_IMPL_NO_RTTI + static const std::type_info& type() noexcept + { + return typeid(T); + } +#endif + +#if 1 // tinyusdz + static uint32_t type_id() noexcept + { + return tinyusdz::value::TypeTraits::type_id(); + } + + static uint32_t underlying_type_id() noexcept + { + return tinyusdz::value::TypeTraits::underlying_type_id(); + } + + static const std::string type_name() noexcept + { + return tinyusdz::value::TypeTraits::type_name(); + } + + static const std::string underlying_type_name() noexcept + { + return tinyusdz::value::TypeTraits::underlying_type_name(); + } +#endif + + static void destroy(storage_union& storage) noexcept + { + //assert(reinterpret_cast(storage.dynamic)); + delete reinterpret_cast(storage.dynamic); + } + + static void copy(const storage_union& src, storage_union& dest) + { + dest.dynamic = new T(*reinterpret_cast(src.dynamic)); + } + + static void move(storage_union& src, storage_union& dest) noexcept + { + dest.dynamic = src.dynamic; + src.dynamic = nullptr; + } + + static void swap(storage_union& lhs, storage_union& rhs) noexcept + { + // just exchage the storage pointers. + std::swap(lhs.dynamic, rhs.dynamic); + } + }; + + /// VTable for stack allocated storage. + template + struct vtable_stack + { +#ifndef ANY_IMPL_NO_RTTI + static const std::type_info& type() noexcept + { + return typeid(T); + } +#endif + +#if 1 // tinyusdz + static uint32_t type_id() noexcept + { + return tinyusdz::value::TypeTraits::type_id(); + } + + static uint32_t underlying_type_id() noexcept + { + return tinyusdz::value::TypeTraits::underlying_type_id(); + } + + static const std::string type_name() noexcept + { + return tinyusdz::value::TypeTraits::type_name(); + } + + static const std::string underlying_type_name() noexcept + { + return tinyusdz::value::TypeTraits::underlying_type_name(); + } +#endif + + static void destroy(storage_union& storage) noexcept + { + reinterpret_cast(&storage.stack)->~T(); + } + + static void copy(const storage_union& src, storage_union& dest) + { + new (&dest.stack) T(reinterpret_cast(src.stack)); + } + + static void move(storage_union& src, storage_union& dest) noexcept + { + // one of the conditions for using vtable_stack is a nothrow move constructor, + // so this move constructor will never throw a exception. + new (&dest.stack) T(std::move(reinterpret_cast(src.stack))); + destroy(src); + } + + static void swap(storage_union& lhs, storage_union& rhs) noexcept + { + storage_union tmp_storage; + move(rhs, tmp_storage); + move(lhs, rhs); + move(tmp_storage, lhs); + } + }; + + /// Whether the type T must be dynamically allocated or can be stored on the stack. + template + struct requires_allocation : + std::integral_constant::value // N4562 §6.3/3 [any.class] + && sizeof(T) <= sizeof(storage_union::stack) + && std::alignment_of::value <= std::alignment_of::value)> + {}; + + /// Returns the pointer to the vtable of the type T. + template + static vtable_type* vtable_for_type() + { + using VTableType = typename std::conditional::value, vtable_dynamic, vtable_stack>::type; + static vtable_type table = { +#ifndef ANY_IMPL_NO_RTTI + VTableType::type, +#endif +#if 1 + VTableType::type_id, + VTableType::underlying_type_id, + VTableType::type_name, + VTableType::underlying_type_name, +#endif + VTableType::destroy, + VTableType::copy, VTableType::move, + VTableType::swap, + }; + return &table; + } + +protected: + template + friend const T* any_cast(const any* operand) noexcept; + template + friend T* any_cast(any* operand) noexcept; + +#ifndef ANY_IMPL_NO_RTTI + /// Same effect as is_same(this->type(), t); + bool is_typed(const std::type_info& t) const + { + return is_same(this->type(), t); + } +#endif + +#ifndef ANY_IMPL_NO_RTTI + /// Checks if two type infos are the same. + /// + /// If ANY_IMPL_FAST_TYPE_INFO_COMPARE is defined, checks only the address of the + /// type infos, otherwise does an actual comparision. Checking addresses is + /// only a valid approach when there's no interaction with outside sources + /// (other shared libraries and such). + static bool is_same(const std::type_info& a, const std::type_info& b) + { +#ifdef ANY_IMPL_FAST_TYPE_INFO_COMPARE + return &a == &b; +#else + return a == b; +#endif + } +#endif + + +private: + storage_union storage; // on offset(0) so no padding for align + vtable_type* vtable; + + template + typename std::enable_if::value>::type + do_construct(ValueType&& value) + { + storage.dynamic = new T(std::forward(value)); + } + + template + typename std::enable_if::value>::type + do_construct(ValueType&& value) + { + new (&storage.stack) T(std::forward(value)); + } + + /// Chooses between stack and dynamic allocation for the type decay_t, + /// assigns the correct vtable, and constructs the object on our storage. + template + void construct(ValueType&& value) + { + using T = typename std::decay::type; + + this->vtable = vtable_for_type(); + + do_construct(std::forward(value)); + } +}; + + + +namespace detail +{ + template + inline ValueType any_cast_move_if_true(typename std::remove_reference::type* p, std::true_type) + { + return std::move(*p); + } + + template + inline ValueType any_cast_move_if_true(typename std::remove_reference::type* p, std::false_type) + { + return *p; + } +} + +/// Performs *any_cast>>(&operand), or throws bad_any_cast on failure. +template +inline ValueType any_cast(const any& operand) +{ + auto p = any_cast::type>::type>(&operand); +#ifndef ANY_IMPL_NO_EXCEPTIONS + if(p == nullptr) throw bad_any_cast(); +#endif + return *p; +} + +/// Performs *any_cast>(&operand), or throws bad_any_cast on failure. +template +inline ValueType any_cast(any& operand) +{ + auto p = any_cast::type>(&operand); +#ifndef ANY_IMPL_NO_EXCEPTIONS + if(p == nullptr) throw bad_any_cast(); +#endif + return *p; +} + +/// +/// If ValueType is MoveConstructible and isn't a lvalue reference, performs +/// std::move(*any_cast>(&operand)), otherwise +/// *any_cast>(&operand). Throws bad_any_cast on failure. +/// +template +inline ValueType any_cast(any&& operand) +{ + using can_move = std::integral_constant::value + && !std::is_lvalue_reference::value>; + + auto p = any_cast::type>(&operand); +#ifndef ANY_IMPL_NO_EXCEPTIONS + if(p == nullptr) throw bad_any_cast(); +#endif + return detail::any_cast_move_if_true(p, can_move()); +} + +/// If operand != nullptr && operand->type() == typeid(ValueType), a pointer to the object +/// contained by operand, otherwise nullptr. +template +inline const ValueType* any_cast(const any* operand) noexcept +{ + using T = typename std::decay::type; + +#ifndef ANY_IMPL_NO_RTTI + if (operand && operand->is_typed(typeid(T))) +#else + if (operand && operand->vtable == any::vtable_for_type()) +#endif + return operand->cast(); + else + return nullptr; +} + +/// If operand != nullptr && operand->type() == typeid(ValueType), a pointer to the object +/// contained by operand, otherwise nullptr. +template +inline ValueType* any_cast(any* operand) noexcept +{ + using T = typename std::decay::type; + +#ifndef ANY_IMPL_NO_RTTI + if (operand && operand->is_typed(typeid(T))) +#else + //if (operand && operand->vtable == any::vtable_for_type()) + if (operand && operand->type_id() == tinyusdz::value::TypeTraits::type_id()) +#endif + return operand->cast(); + else + return nullptr; +} + +// Force cast(no type check) +template +inline const ValueType* cast(const any* operand) noexcept +{ + return operand->cast(); +} + +template +inline ValueType* cast(any* operand) noexcept +{ + return operand->cast(); +} + +} + +namespace std +{ + inline void swap(linb::any& lhs, linb::any& rhs) noexcept + { + lhs.swap(rhs); + } +} + +#endif diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tiny-format.cc b/contrib/tinyusdz/tinyusdz_repo/src/tiny-format.cc new file mode 100644 index 000000000..4ebb9b342 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tiny-format.cc @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +// Copyright 2022-Present Syoyo Fujita. +#include "tiny-format.hh" + +namespace tinyusdz { +namespace fmt { + +namespace detail { + +nonstd::expected, std::string> tokenize( + const std::string &s) { + size_t n = s.length(); + + bool open_curly_brace = false; + + std::vector toks; + size_t si = 0; + + for (size_t i = 0; i < n; i++) { + if (s[i] == '{') { + if (open_curly_brace) { + // nested '{' + return nonstd::make_unexpected("Nested '{'."); + } + + open_curly_brace = true; + + if (si >= i) { // previous char is '}' + // do nothing + } else { + toks.push_back( + std::string(s.begin() + std::string::difference_type(si), + s.begin() + std::string::difference_type(i))); + + si = i; + } + + } else if (s[i] == '}') { + if (open_curly_brace) { + // must be "{}" for now + if ((i - si) > 1) { + return nonstd::make_unexpected( + "Format specifier in '{}' is not yet supported."); + } + + open_curly_brace = false; + + toks.push_back("{}"); + + si = i + 1; // start from next char. + + } else { + // Currently we allow string like '}', "}}", "bora}". + // TODO: strict check for '{' pair. + } + } + } + + if (si < n) { + toks.push_back(std::string(s.begin() + std::string::difference_type(si), + s.begin() + std::string::difference_type(n))); + } + + return std::move(toks); +} + +std::ostringstream &format_sv(std::ostringstream &ss, + const std::vector &sv) { + if (sv.empty()) { + return ss; + } + + for (const auto &item : sv) { + ss << item; + } + + return ss; +} + +} // namespace detail + +std::string format(const std::string &in) { return in; } + +} // namespace fmt +} // namespace tinyusdz + +#if 0 +void test(const std::string &in) { + std::cout << tinyusdz::fmt::format(in) << "\n"; + std::cout << tinyusdz::fmt::format(in, 1.0f) << "\n"; + std::cout << tinyusdz::fmt::format(in, 1.0f, 2.0f) << "\n"; + std::cout << tinyusdz::fmt::format(in, 1.0f, 2.0f, 3.0f) << "\n"; +} + +int main(int argc, char **argv) { + test("{}"); + test("{"); + test("}"); + test("{{"); + test("}}"); + test("{a}"); + test("bora {}"); + test("{} dora"); + test("{} dora{} bora muda {"); + test("{} dora{} bora muda{}"); + + return 0; +} +#endif diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tiny-format.hh b/contrib/tinyusdz/tinyusdz_repo/src/tiny-format.hh new file mode 100644 index 000000000..e9f67bbb2 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tiny-format.hh @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT +// Copyright 2022-Present Syoyo Fujita. + +/// +/// Simple Python-like format print utility in C++11 or later. Only supports +/// "{}". +/// +#pragma once + +#include +#include +#include + +#include "nonstd/expected.hpp" + +namespace tinyusdz { +namespace fmt { + +namespace detail { + +template +std::ostringstream &format_sv_rec(std::ostringstream &ss, + const std::vector &sv, + size_t idx, T const &v) { + if (idx >= sv.size()) { + return ss; + } + + // Print remaininig items + bool fmt_printed{false}; + + for (size_t i = idx; i < sv.size(); i++) { + if (sv[i] == "{}") { + if (fmt_printed) { + ss << sv[i]; + } else { + ss << v; + fmt_printed = true; + } + } else { + ss << sv[i]; + } + } + + return ss; +} + +template +std::ostringstream &format_sv_rec(std::ostringstream &ss, + const std::vector &sv, + size_t idx, T const &v, Rest const &...args) { + if (idx >= sv.size()) { + return ss; + } + + if (sv[idx] == "{}") { + ss << v; + format_sv_rec(ss, sv, idx + 1, args...); + } else { + ss << sv[idx]; + format_sv_rec(ss, sv, idx + 1, v, args...); + } + + return ss; +} + +template +std::ostringstream &format_sv(std::ostringstream &ss, + const std::vector &sv, + Args const &...args) { + format_sv_rec(ss, sv, 0, args...); + + return ss; +} + +std::ostringstream &format_sv(std::ostringstream &ss, + const std::vector &sv); + +nonstd::expected, std::string> tokenize( + const std::string &s); + +} // namespace detail + +template +std::string format(const std::string &in, Args const &...args) { + auto ret = detail::tokenize(in); + if (!ret) { + return in + "(format error: " + ret.error() + ")"; + } + + std::ostringstream ss; + detail::format_sv(ss, (*ret), args...); + + return ss.str(); +} + +std::string format(const std::string &in); + +} // namespace fmt +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tiny-variant.hh b/contrib/tinyusdz/tinyusdz_repo/src/tiny-variant.hh new file mode 100644 index 000000000..9c5c75f5d --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tiny-variant.hh @@ -0,0 +1,356 @@ +// Based on +// https://gist.github.com/calebh/fd00632d9c616d4b0c14e7c2865f3085 +// +// Modification by Syoyo Fujita. +// - Use tinyusdz::value::TypeTraits for type_id +// - Disable exception +// - Implement set and get, get_if +// + +/* +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +For more information, please refer to +*/ +#pragma once + +#include +#include + +#include "value-types.hh" // import TypeTraits + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#include "nonstd/optional.hpp" // for optional& get() + + +namespace tinyusdz { + +namespace variant_detail { + +// Equivalent to std::aligned_storage +template +struct aligned_storage { + struct type { + alignas(Align) unsigned char data[Len]; + }; +}; + +template +struct static_max; + +template +struct static_max { + static const unsigned int value = arg; +}; + +template +struct static_max { + static const unsigned int value = arg1 >= arg2 + ? static_max::value + : static_max::value; +}; + +template +struct remove_reference { + typedef T type; +}; +template +struct remove_reference { + typedef T type; +}; +template +struct remove_reference { + typedef T type; +}; + +template +struct variant_helper_rec; + +template +struct variant_helper_rec { + inline static void destroy(uint32_t id, void* data) { + if (value::TypeTraits::type_id == id) { + reinterpret_cast(data)->~F(); + } else { + variant_helper_rec::destroy(id, data); + } + } + + inline static void move(uint32_t id, void* from, void* to) { + if (value::TypeTraits::type_id == id) { + // This static_cast and use of remove_reference is equivalent to the use + // of std::move + new (to) F(static_cast::type&&>( + *reinterpret_cast(from))); + } else { + variant_helper_rec::move(id, from, to); + } + } + + inline static void copy(uint32_t id, const void* from, void* to) { + if (value::TypeTraits::type_id == id) { + new (to) F(*reinterpret_cast(from)); + } else { + variant_helper_rec::copy(id, from, to); + } + } +}; + +template <> +struct variant_helper_rec<> { + inline static void destroy(uint32_t id, void* data) {} + inline static void move(uint32_t old_t, void* from, void* to) {} + inline static void copy(uint32_t old_t, const void* from, void* to) {} +}; + +template +struct variant_helper { + inline static void destroy(uint32_t id, void* data) { + variant_helper_rec::destroy(id, data); + } + + inline static void move(uint32_t id, void* from, void* to) { + variant_helper_rec::move(id, from, to); + } + + inline static void copy(uint32_t id, const void* old_v, void* new_v) { + variant_helper_rec::copy(id, old_v, new_v); + } +}; + +template <> +struct variant_helper<> { + inline static void destroy(uint32_t id, void* data) {} + inline static void move(uint32_t old_t, void* old_v, void* new_v) {} + inline static void copy(uint32_t old_t, const void* old_v, void* new_v) {} +}; + +template +struct variant_helper_static; + +template +struct variant_helper_static { + inline static void move(void* from, void* to) { + new (to) F(static_cast::type&&>( + *reinterpret_cast(from))); + } + + inline static void copy(const void* from, void* to) { + new (to) F(*reinterpret_cast(from)); + } +}; + +#if 0 // not used +// Given a uint8_t i, selects the ith type from the list of item types +template +struct variant_alternative; + +template +struct variant_alternative<0, HeadItem, TailItems...> { + using type = HeadItem; +}; + +template +struct variant_alternative { + using type = typename variant_alternative::type; +}; +#endif + +template +struct variant_get_rec; + +template +struct is_one_of { + static constexpr bool value = false; +}; + +template +struct is_one_of { + static constexpr bool value = + std::is_same::value || is_one_of::value; +}; + +} // namespace variant_detail + +using namespace variant_detail; + +template +struct variant { + + private: + static const unsigned int data_size = static_max::value; + static const unsigned int data_align = static_max::value; + + using data_t = typename aligned_storage::type; + + using helper_t = variant_helper; + + // template + // using alternative = typename variant_alternative::type; + + static inline uint32_t invalid_type() { + return value::TypeTraits::type_id(); + } + + uint32_t variant_id; + data_t data; + + static void *nulldata() { + return nullptr; + } + + public: + + variant() : variant_id(invalid_type()) {} + + + variant(const variant& from) : variant_id(from.variant_id) { + helper_t::copy(from.variant_id, &from.data, &data); + } + + variant(variant&& from) : variant_id(from.variant_id) { + helper_t::move(from.variant_id, &from.data, &data); + } + + variant& operator=(const variant& rhs) { + helper_t::destroy(variant_id, &data); + variant_id = rhs.variant_id; + helper_t::copy(rhs.variant_id, &rhs.data, &data); + return *this; + } + + variant& operator=(variant&& rhs) { + helper_t::destroy(variant_id, &data); + variant_id = rhs.variant_id; + helper_t::move(rhs.variant_id, &rhs.data, &data); + return *this; + } + + template + bool is() const { + return variant_id == value::TypeTraits::type_id; + } + + uint32_t id() const { return variant_id; } + + // template + template ::value, void>::type> + void set(Args&&... args) { + helper_t::destroy(variant_id, &data); + new (&data) T(std::forward(args)...); + variant_id = value::TypeTraits::type_id; + // variant_helper_static>::copy(&value, &data); + } + + template + variant(const T &v) { + set(v); + } + +#if 1 + // allow seg fault. + template ::value, void>::type> + T& cast() { + // It is a dynamic_cast-like behaviour + if (variant_id == value::TypeTraits::type_id) { + return *reinterpret_cast(&data); + } + + // Will raise null-pointer dereference error. + return *reinterpret_cast(nulldata()); + } +#endif + + template ::value, void>::type> + const nonstd::optional get() { + // It is a dynamic_cast-like behaviour + if (variant_id == value::TypeTraits::type_id) { + return *reinterpret_cast(&data); + } + + return nonstd::nullopt; + } + + template ::value, void>::type> + const nonstd::optional get() const { + // It is a dynamic_cast-like behaviour + if (variant_id == value::TypeTraits::type_id) { + return *reinterpret_cast(&data); + } + + return nonstd::nullopt; + } + + template ::value, void>::type> + const T* get_if() const { + // It is a dynamic_cast-like behaviour + if (variant_id == value::TypeTraits::type_id) { + return reinterpret_cast(&data); + } + + return nullptr; + } + + ~variant() { helper_t::destroy(variant_id, &data); } +}; + +struct monostate {}; + +namespace value { + +#define DEFINE_TYPE_TRAIT(__dty, __name, __tyid, __nc) \ + template <> \ + struct TypeTraits<__dty> { \ + using value_type = __dty; \ + using value_underlying_type = __dty; \ + static constexpr uint32_t ndim = 0; /* array dim */ \ + static constexpr uint32_t ncomp = \ + __nc; /* the number of components(e.g. float3 => 3) */ \ + static constexpr uint32_t type_id = __tyid; \ + static constexpr uint32_t underlying_type_id = __tyid; \ + static std::string type_name() { return __name; } \ + static std::string underlying_type_name() { return __name; } \ + } + +DEFINE_TYPE_TRAIT(monostate, "monostate", TYPE_ID_MONOSTATE, 1); + +#undef DEFINE_TYPE_TRAIT + +} // namespace value + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tinyusdz.cc b/contrib/tinyusdz/tinyusdz_repo/src/tinyusdz.cc new file mode 100644 index 000000000..329da3184 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tinyusdz.cc @@ -0,0 +1,1424 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2019 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertinament Inc. + +#include +#include +//#include +#include // std::tolower +#include +#include +#include +#include + +#include "usdLux.hh" + +#ifndef __wasi__ +#include +#endif + +#include +#include +#include +#include + +#include "image-loader.hh" +#include "integerCoding.h" +#include "io-util.hh" +#include "lz4-compression.hh" +#include "pprinter.hh" +#include "str-util.hh" +#include "stream-reader.hh" +#include "tiny-format.hh" +#include "tinyusdz.hh" +#include "usda-reader.hh" +#include "usdc-reader.hh" +#include "value-pprint.hh" + +#if 0 +#if defined(TINYUSDZ_WITH_AUDIO) + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#define DR_WAV_IMPLEMENTATION +#include "external/dr_wav.h" + +#define DR_MP3_IMPLEMENTATION +#include "external/dr_mp3.h" + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#endif // TINYUSDZ_WITH_AUDIO + +#if defined(TINYUSDZ_WITH_OPENSUBDIV) + +#include "subdiv.hh" + +#endif + +#endif + +#include "common-macros.inc" + +namespace tinyusdz { + +// constexpr auto kTagUSDA = "[USDA]"; +// constexpr auto kTagUSDC = "[USDC]"; +// constexpr auto kTagUSDZ = "[USDZ]"; + +// For PUSH_ERROR_AND_RETURN +#define PushError(s) \ + if (err) { \ + (*err) += s; \ + } +//#define PushWarn(s) if (warn) { (*warn) += s; } + +bool LoadUSDCFromMemory(const uint8_t *addr, const size_t length, + const std::string &filename, Stage *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options) { + if (stage == nullptr) { + if (err) { + (*err) = "null pointer for `stage` argument.\n"; + } + return false; + } + + bool swap_endian = false; // @FIXME + + size_t max_length; + + // 32bit env + if (sizeof(void *) == 4) { + if (options.max_memory_limit_in_mb > 4096) { // exceeds 4GB + max_length = std::numeric_limits::max(); + } else { + max_length = + size_t(1024) * size_t(1024) * size_t(options.max_memory_limit_in_mb); + } + } else { + // TODO: Set hard limit? + max_length = + size_t(1024) * size_t(1024) * size_t(options.max_memory_limit_in_mb); + } + + DCOUT("Max length = " << max_length); + + if (length > max_length) { + if (err) { + (*err) += "USDC data [" + filename + + "] is too large(size = " + std::to_string(length) + + ", which exceeds memory limit " + std::to_string(max_length) + + ".\n"; + } + + return false; + } + + StreamReader sr(addr, length, swap_endian); + + usdc::USDCReaderConfig config; + config.numThreads = options.num_threads; + config.strict_allowedToken_check = options.strict_allowedToken_check; + usdc::USDCReader reader(&sr, config); + + if (!reader.ReadUSDC()) { + if (warn) { + (*warn) = reader.GetWarning(); + } + + if (err) { + (*err) = reader.GetError(); + } + return false; + } + + DCOUT("Loaded USDC file."); + + // Reconstruct `Stage`(scene) object + { + if (!reader.ReconstructStage(stage)) { + DCOUT("Failed to reconstruct Stage from Crate."); + if (warn) { + (*warn) = reader.GetWarning(); + } + + if (err) { + (*err) = reader.GetError(); + } + return false; + } + } + + if (warn) { + (*warn) = reader.GetWarning(); + } + + // Reconstruct OK but may have some error. + // TODO(syoyo): Return false in strict mode. + if (err) { + DCOUT(reader.GetError()); + (*err) = reader.GetError(); + } + + DCOUT("Reconstructed Stage from USDC file."); + + return true; +} + +bool LoadUSDCFromFile(const std::string &_filename, Stage *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options) { + std::string filepath = io::ExpandFilePath(_filename, /* userdata */ nullptr); + + std::vector data; + size_t max_bytes = 1024 * 1024 * size_t(options.max_memory_limit_in_mb); + if (!io::ReadWholeFile(&data, err, filepath, max_bytes, + /* userdata */ nullptr)) { + if (err) { + (*err) += "File not found or failed to read : \"" + filepath + "\"\n"; + } + + return false; + } + + DCOUT("File size: " + std::to_string(data.size()) + " bytes."); + + if (data.size() < (11 * 8)) { + // ??? + if (err) { + (*err) += "File size too short. Looks like this file is not a USDC : \"" + + filepath + "\"\n"; + } + return false; + } + + return LoadUSDCFromMemory(data.data(), data.size(), filepath, stage, warn, + err, options); +} + +namespace { + +static std::string GetFileExtension(const std::string &filename) { + if (filename.find_last_of('.') != std::string::npos) + return filename.substr(filename.find_last_of('.') + 1); + return ""; +} + +static std::string str_tolower(std::string s) { + std::transform(s.begin(), s.end(), s.begin(), + [](unsigned char c) { return std::tolower(c); }); + return s; +} + +} // namespace + +namespace { + +struct USDZAssetInfo { + std::string filename; + size_t byte_begin; + size_t byte_end; +}; + +bool ParseUSDZHeader(const uint8_t *addr, const size_t length, + std::vector *assets, std::string *warn, + std::string *err) { + (void)warn; + + if (!addr) { + if (err) { + (*err) += "null for `addr` argument.\n"; + } + return false; + } + + if (length < (11 * 8) + 30) { // 88 for USDC header, 30 for ZIP header + // ??? + if (err) { + (*err) += "File size too short. Looks like this file is not a USDZ\n"; + } + return false; + } + + size_t offset = 0; + while ((offset + 30) < length) { + // + // PK zip format: + // https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip.html + // + std::vector local_header(30); + memcpy(local_header.data(), addr + offset, 30); + + // Check signagure(first 4 bytes) + // Must be \x50\x4b\x03\x04 + if ((local_header[0] == 0x50) && (local_header[1] == 0x4b) && + (local_header[2] == 0x03) && (local_header[3] == 0x04)) { + // ok + + // TODO: Check other header info(version, flags, crc32) + } else { + if (offset == 0) { + // Invalid header found. + if (err) { + (*err) += "PKZIP header not found.\n"; + } + return false; + } else { + // not a local(global?) header + // Maybe near to the end of file. + break; + } + } + + offset += 30; + + // read in the variable name + uint16_t name_len; + memcpy(&name_len, &local_header[26], sizeof(uint16_t)); + if ((offset + name_len) > length) { + if (err) { + (*err) += "Invalid ZIP data\n"; + } + return false; + } + + std::string varname(name_len, ' '); + memcpy(&varname[0], addr + offset, name_len); + + offset += name_len; + + // read in the extra field + uint16_t extra_field_len; + memcpy(&extra_field_len, &local_header[28], sizeof(uint16_t)); + if (extra_field_len > 0) { + if (offset + extra_field_len > length) { + if (err) { + (*err) += "Invalid extra field length in ZIP data\n"; + } + return false; + } + } + + offset += extra_field_len; + + // In usdz, data must be aligned at 64bytes boundary. + if ((offset % 64) != 0) { + if (err) { + (*err) += "Data offset must be mulitple of 64bytes for USDZ, but got " + + std::to_string(offset) + ".\n"; + } + return false; + } + + uint16_t compr_method = *reinterpret_cast(&local_header[0] + 8); + // uint32_t compr_bytes = *reinterpret_cast(&local_header[0]+18); + uint32_t uncompr_bytes; + memcpy(&uncompr_bytes, &local_header[22], sizeof(uncompr_bytes)); + + // USDZ only supports uncompressed ZIP + if (compr_method != 0) { + if (err) { + (*err) += "Compressed ZIP is not supported for USDZ\n"; + } + return false; + } + + if (assets) { + USDZAssetInfo info; + DCOUT("USDZasset[" << assets->size() << "] " << varname << ", byte_begin " << offset << ", length " << uncompr_bytes << "\n"); + info.filename = varname; + info.byte_begin = offset; + info.byte_end = offset + uncompr_bytes; + + assets->push_back(info); + } + + offset += uncompr_bytes; + } + + return true; +} + +} // namespace + +bool LoadUSDZFromMemory(const uint8_t *addr, const size_t length, + const std::string &filename, Stage *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options) { + std::vector assets; + if (!ParseUSDZHeader(addr, length, &assets, warn, err)) { + return false; + } + +#ifdef TINYUSDZ_LOCAL_DEBUG_PRINT + for (size_t i = 0; i < assets.size(); i++) { + DCOUT("[" << i << "] " << assets[i].filename << " : byte range (" + << assets[i].byte_begin << ", " << assets[i].byte_end << ")"); + } +#endif + + int32_t usdc_index = -1; + int32_t usda_index = -1; + { + bool warned = false; // to report single warning message. + for (size_t i = 0; i < assets.size(); i++) { + std::string ext = str_tolower(GetFileExtension(assets[i].filename)); + if (ext.compare("usdc") == 0) { + if ((usdc_index > -1) && (!warned)) { + if (warn) { + (*warn) += + "Multiple USDC files were found in USDZ. Use the first found " + "one: " + + assets[size_t(usdc_index)].filename + "]\n"; + } + warned = true; + } + + if (usdc_index == -1) { + usdc_index = int32_t(i); + } + } else if (ext.compare("usda") == 0) { + if ((usda_index > -1) && (!warned)) { + if (warn) { + (*warn) += + "Multiple USDA files were found in USDZ. Use the first found " + "one: " + + assets[size_t(usda_index)].filename + "]\n"; + } + warned = true; + } + if (usda_index == -1) { + usda_index = int32_t(i); + } + } + } + } + + if ((usdc_index == -1) && (usda_index == -1)) { + if (err) { + (*err) += "Neither USDC nor USDA file found in USDZ\n"; + } + return false; + } + + if ((usdc_index >= 0) && (usda_index >= 0)) { + if (warn) { + (*warn) += "Both USDA and USDC file found. Use USDC file [" + + assets[size_t(usdc_index)].filename + "]\n"; + } + } + + if (usdc_index >= 0) { + const size_t start_addr_offset = assets[size_t(usdc_index)].byte_begin; + const size_t end_addr_offset = assets[size_t(usdc_index)].byte_end; + if (end_addr_offset < start_addr_offset) { + if (err) { + (*err) += + "Invalid start/end offset to USDC data: [" + filename + "].\n"; + } + return false; + } + const size_t usdc_size = end_addr_offset - start_addr_offset; + + if (start_addr_offset > length) { + if (err) { + (*err) += "Invalid start offset to USDC data: [" + filename + "].\n"; + } + return false; + } + + if (end_addr_offset > length) { + if (err) { + (*err) += "Invalid end offset to USDC data: [" + filename + "].\n"; + } + return false; + } + + const uint8_t *usdc_addr = addr + start_addr_offset; + bool ret = LoadUSDCFromMemory(usdc_addr, usdc_size, filename, stage, warn, + err, options); + + if (!ret) { + if (err) { + (*err) += "Failed to load USDC: [" + filename + "].\n"; + } + + return false; + } + } else if (usda_index >= 0) { + const size_t start_addr_offset = assets[size_t(usda_index)].byte_begin; + const size_t end_addr_offset = assets[size_t(usda_index)].byte_end; + if (end_addr_offset < start_addr_offset) { + if (err) { + (*err) += + "Invalid start/end offset to USDA data: [" + filename + "].\n"; + } + return false; + } + const size_t usda_size = end_addr_offset - start_addr_offset; + + if (start_addr_offset > length) { + if (err) { + (*err) += "Invalid start offset to USDA data: [" + filename + "].\n"; + } + return false; + } + + if (end_addr_offset > length) { + if (err) { + (*err) += "Invalid end offset to USDA data: [" + filename + "].\n"; + } + return false; + } + + const uint8_t *usda_addr = addr + start_addr_offset; + bool ret = LoadUSDAFromMemory(usda_addr, usda_size, filename, stage, warn, + err, options); + + if (!ret) { + if (err) { + (*err) += "Failed to load USDA: [" + filename + "].\n"; + } + + return false; + } + } + +#if 0 // TODO: Remove + // Decode images + for (size_t i = 0; i < assets.size(); i++) { + const std::string &uri = assets[i].filename; + const std::string ext = GetFileExtension(uri); + + if ((ext.compare("png") == 0) || (ext.compare("jpg") == 0) || + (ext.compare("jpeg") == 0)) { + const size_t start_addr_offset = assets[i].byte_begin; + const size_t end_addr_offset = assets[i].byte_end; + const size_t asset_size = end_addr_offset - start_addr_offset; + const uint8_t *asset_addr = addr + start_addr_offset; + + if (end_addr_offset < start_addr_offset) { + if (err) { + (*err) += "Invalid start/end offset of asset #" + std::to_string(i) + + " in USDZ data: [" + filename + "].\n"; + } + return false; + } + + if (start_addr_offset > length) { + if (err) { + (*err) += "Invalid start offset of asset #" + std::to_string(i) + + " in USDZ data: [" + filename + "].\n"; + } + return false; + } + + if (end_addr_offset > length) { + if (err) { + (*err) += "Invalid end offset of asset #" + std::to_string(i) + + " in USDZ data: [" + filename + "].\n"; + } + return false; + } + + if (asset_size > (options.max_allowed_asset_size_in_mb * 1024ull * 1024ull)) { + PUSH_ERROR_AND_RETURN_TAG(kTagUSDZ, fmt::format("Asset no[{}] file size too large. {} bytes (max_allowed_asset_size {})", + i, asset_size, options.max_allowed_asset_size_in_mb * 1024ull * 1024ull)); + } + + DCOUT("Image asset size: " << asset_size); + + { + nonstd::expected info = + image::GetImageInfoFromMemory(asset_addr, asset_size, uri); + + if (info) { + if (info->width == 0) { + PUSH_ERROR_AND_RETURN_TAG(kTagUSDZ, fmt::format("Assset no[{}] Image has zero width.", i)); + } + + if (info->width > options.max_image_width) { + PUSH_ERROR_AND_RETURN_TAG( + kTagUSDZ, fmt::format("Asset no[{}] Image width too large. {} (max_image_width {})", i, info->width, options.max_image_width)); + } + + if (info->height == 0) { + PUSH_ERROR_AND_RETURN_TAG(kTagUSDZ, fmt::format("Asset no[{}] Image has zero height.", i)); + } + + if (info->height > options.max_image_height) { + PUSH_ERROR_AND_RETURN_TAG( + kTagUSDZ, + fmt::format("Asset no[{}] Image height too large. {} (max_image_height {})", i, info->height, options.max_image_height)); + } + + if (info->channels == 0) { + PUSH_ERROR_AND_RETURN_TAG(kTagUSDZ, fmt::format("Asset no[{}] Image has zero channels.", i)); + } + + if (info->channels > options.max_image_channels) { + PUSH_ERROR_AND_RETURN_TAG( + kTagUSDZ, + fmt::format("Asset no[{}] Image channels too much", i)); + } + } + } + + Image image; + nonstd::expected ret = + image::LoadImageFromMemory(asset_addr, asset_size, uri); + + if (!ret) { + (*err) += ret.error(); + } else { + image = (*ret).image; + if (!(*ret).warning.empty()) { + (*warn) += (*ret).warning; + } + } + } else { + // TODO: Support other asserts(e.g. audio mp3) + } + } +#endif + + return true; +} + +bool LoadUSDZFromFile(const std::string &_filename, Stage *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options) { + // + std::vector> assets; + + std::string filepath = io::ExpandFilePath(_filename, /* userdata */ nullptr); + + std::vector data; + size_t max_bytes = 1024 * 1024 * size_t(options.max_memory_limit_in_mb); + if (!io::ReadWholeFile(&data, err, filepath, max_bytes, + /* userdata */ nullptr)) { + return false; + } + + if (data.size() < (11 * 8) + 30) { // 88 for USDC header, 30 for ZIP header + // ??? + if (err) { + (*err) += "File size too short. Looks like this file is not a USDZ : \"" + + filepath + "\"\n"; + } + return false; + } + + return LoadUSDZFromMemory(data.data(), data.size(), filepath, stage, warn, + err, options); +} + +#ifdef _WIN32 +bool LoadUSDZFromFile(const std::wstring &_filename, Stage *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options) { + std::string filename = io::WcharToUTF8(_filename); + return LoadUSDZFromFile(filename, stage, warn, err, options); +} +#endif + +bool LoadUSDAFromMemory(const uint8_t *addr, const size_t length, + const std::string &base_dir, Stage *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options) { + if (addr == nullptr) { + if (err) { + (*err) = "null pointer for `addr` argument.\n"; + } + return false; + } + + if (stage == nullptr) { + if (err) { + (*err) = "null pointer for `stage` argument.\n"; + } + return false; + } + + tinyusdz::StreamReader sr(addr, length, /* swap endian */ false); + tinyusdz::usda::USDAReader reader(&sr); + + tinyusdz::usda::USDAReaderConfig config; + config.strict_allowedToken_check = options.strict_allowedToken_check; + reader.set_reader_config(config); + + reader.SetBaseDir(base_dir); + + { + bool ret = reader.Read(); + + if (!ret) { + if (err) { + (*err) += "Failed to parse USDA\n"; + (*err) += reader.GetError(); + } + + return false; + } + } + + { + bool ret = reader.ReconstructStage(); + if (!ret) { + if (err) { + (*err) += "Failed to reconstruct Stage from USDA:\n"; + (*err) += reader.GetError() + "\n"; + } + return false; + } + } + + (*stage) = reader.GetStage(); + + if (warn) { + (*warn) += reader.GetWarning(); + } + + return true; +} + +bool LoadUSDAFromFile(const std::string &_filename, Stage *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options) { + std::string filepath = io::ExpandFilePath(_filename, /* userdata */ nullptr); + std::string base_dir = io::GetBaseDir(_filename); + + std::vector data; + size_t max_bytes = 1024 * 1024 * size_t(options.max_memory_limit_in_mb); + if (!io::ReadWholeFile(&data, err, filepath, max_bytes, + /* userdata */ nullptr)) { + if (err) { + (*err) += "File not found or failed to read : \"" + filepath + "\"\n"; + } + } + + return LoadUSDAFromMemory(data.data(), data.size(), base_dir, stage, warn, + err, options); +} + +bool LoadUSDFromFile(const std::string &_filename, Stage *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options) { + std::string filepath = io::ExpandFilePath(_filename, /* userdata */ nullptr); + std::string base_dir = io::GetBaseDir(_filename); + + std::vector data; + size_t max_bytes = 1024 * 1024 * size_t(options.max_memory_limit_in_mb); + if (!io::ReadWholeFile(&data, err, filepath, max_bytes, + /* userdata */ nullptr)) { + return false; + } + + return LoadUSDFromMemory(data.data(), data.size(), base_dir, stage, warn, err, + options); +} + +bool LoadUSDFromMemory(const uint8_t *addr, const size_t length, + const std::string &base_dir, Stage *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options) { + if (IsUSDC(addr, length)) { + DCOUT("Detected as USDC."); + return LoadUSDCFromMemory(addr, length, base_dir, stage, warn, err, + options); + } else if (IsUSDA(addr, length)) { + DCOUT("Detected as USDA."); + return LoadUSDAFromMemory(addr, length, base_dir, stage, warn, err, + options); + } else if (IsUSDZ(addr, length)) { + DCOUT("Detected as USDZ."); + return LoadUSDZFromMemory(addr, length, base_dir, stage, warn, err, + options); + } else { + if (err) { + (*err) += "Couldn't determine USD format(USDA/USDC/USDZ).\n"; + } + return false; + } +} + +bool ReadUSDZAssetInfoFromMemory(const uint8_t *addr, const size_t length, const bool asset_on_memory, USDZAsset *asset, + std::string *warn, std::string *err) { + + if (!asset) { + return false; + } + + std::vector assetInfos; + if (!ParseUSDZHeader(addr, length, &assetInfos, warn, err)) { + return false; + } + + for (size_t i = 0; i < assetInfos.size(); i++) { + if (assetInfos[i].byte_begin > length) { + if (err) { + (*err) += "Invalid byte begin offset in USDZ asset header."; + } + return false; + } + if (assetInfos[i].byte_end > length) { + if (err) { + (*err) += "Invalid byte end offset in USDZ asset header."; + } + return false; + } + // Assume same filename does not exist. + asset->asset_map[assetInfos[i].filename] = std::make_pair(assetInfos[i].byte_begin, assetInfos[i].byte_end); + } + + if (asset_on_memory) { + asset->data.clear(); + asset->addr = addr; + asset->size = length; + } else { + // copy content + asset->data.resize(length); + memcpy(asset->data.data(), addr, length); + asset->addr = nullptr; + asset->size = 0; + } + + return true; +} + +bool ReadUSDZAssetInfoFromFile(const std::string &_filename, USDZAsset *asset, + std::string *warn, std::string *err, size_t max_memory_limit_in_mb) { + + std::string filepath = io::ExpandFilePath(_filename, /* userdata */ nullptr); + std::string base_dir = io::GetBaseDir(_filename); + + std::vector data; + size_t max_bytes = 1024ull * 1024ull * max_memory_limit_in_mb; + if (!io::ReadWholeFile(&data, err, filepath, max_bytes, + /* userdata */ nullptr)) { + return false; + } + + return ReadUSDZAssetInfoFromMemory(data.data(), data.size(), /* asset_on_memory */false, asset, warn, err); + +} + +// +// File type detection +// + +bool IsUSDA(const std::string &filename) { + // TODO: Read first few bytes and check the magic number. + // + std::vector data; + std::string err; + // 12 = enough storage for "#usda 1.0" + if (!io::ReadFileHeader(&data, &err, filename, 12, + /* userdata */ nullptr)) { + // TODO: return `err` + return false; + } + + return IsUSDA(data.data(), data.size()); +} + +bool IsUSDA(const uint8_t *addr, const size_t length) { + if (length < 9) { + return false; + } + const char header[9 + 1] = "#usda 1.0"; + + if (memcmp(header, addr, 9) == 0) { + return true; + } + + return false; +} + +bool IsUSDC(const std::string &filename) { + // TODO: Read first few bytes and check the magic number. + // + std::vector data; + std::string err; + // 88 bytes should enough + if (!io::ReadFileHeader(&data, &err, filename, /* header bytes */ 88, + /* userdata */ nullptr)) { + return false; + } + + return IsUSDC(data.data(), data.size()); +} + +bool IsUSDC(const uint8_t *addr, const size_t length) { + // must be 88bytes or more + if (length < 88) { + return false; + } + const char header[8 + 1] = "PXR-USDC"; + + if (memcmp(header, addr, 8) == 0) { + return true; + } + + return false; +} + +bool IsUSDZ(const std::string &filename) { + // TODO: Read first few bytes and check the magic number. + // + std::vector data; + std::string err; + // 256 bytes may be enough. + if (!io::ReadFileHeader(&data, &err, filename, 256, + /* userdata */ nullptr)) { + return false; + } + + return IsUSDZ(data.data(), data.size()); +} + +bool IsUSDZ(const uint8_t *addr, const size_t length) { + std::string warn; + std::string err; + + return ParseUSDZHeader(addr, length, /* [out] assets */ nullptr, &warn, &err); +} + +bool IsUSD(const std::string &filename, std::string *detected_format) { + if (IsUSDA(filename)) { + if (detected_format) { + (*detected_format) = "usda"; + } + return true; + } + + if (IsUSDC(filename)) { + if (detected_format) { + (*detected_format) = "usdc"; + } + return true; + } + + if (IsUSDZ(filename)) { + if (detected_format) { + (*detected_format) = "usdz"; + } + return true; + } + + return false; +} + +bool IsUSD(const uint8_t *addr, const size_t length, std::string *detected_format) { + if (IsUSDA(addr, length)) { + if (detected_format) { + (*detected_format) = "usda"; + } + return true; + } + + if (IsUSDC(addr, length)) { + if (detected_format) { + (*detected_format) = "usdc"; + } + return true; + } + + if (IsUSDZ(addr, length)) { + if (detected_format) { + (*detected_format) = "usdz"; + } + return true; + } + + return false; +} + +bool LoadUSDCLayerFromMemory(const uint8_t *addr, const size_t length, + const std::string &filename, Layer *layer, + std::string *warn, std::string *err, + const USDLoadOptions &options) { + if (layer == nullptr) { + if (err) { + (*err) = "null pointer for `layer` argument.\n"; + } + return false; + } + + bool swap_endian = false; // @FIXME + + size_t max_length; + + // 32bit env + if (sizeof(void *) == 4) { + if (options.max_memory_limit_in_mb > 4096) { // exceeds 4GB + max_length = std::numeric_limits::max(); + } else { + max_length = + size_t(1024) * size_t(1024) * size_t(options.max_memory_limit_in_mb); + } + } else { + // TODO: Set hard limit? + max_length = + size_t(1024) * size_t(1024) * size_t(options.max_memory_limit_in_mb); + } + + DCOUT("Max length = " << max_length); + + if (length > max_length) { + if (err) { + (*err) += "USDC data [" + filename + + "] is too large(size = " + std::to_string(length) + + ", which exceeds memory limit " + std::to_string(max_length) + + ".\n"; + } + + return false; + } + + StreamReader sr(addr, length, swap_endian); + + usdc::USDCReaderConfig config; + config.numThreads = options.num_threads; + config.strict_allowedToken_check = options.strict_allowedToken_check; + usdc::USDCReader reader(&sr, config); + + if (!reader.ReadUSDC()) { + if (warn) { + (*warn) = reader.GetWarning(); + } + + if (err) { + (*err) = reader.GetError(); + } + return false; + } + + DCOUT("Loaded USDC file."); + + { + if (!reader.get_as_layer(layer)) { + DCOUT("Failed to reconstruct Layer from Crate."); + if (warn) { + (*warn) = reader.GetWarning(); + } + + if (err) { + (*err) = reader.GetError(); + } + return false; + } + } + + if (warn) { + (*warn) = reader.GetWarning(); + } + + // Reconstruct OK but may have some error. + // TODO(syoyo): Return false in strict mode. + if (err) { + DCOUT(reader.GetError()); + (*err) = reader.GetError(); + } + + DCOUT("Reconstructed Stage from USDC file."); + + return true; +} + +bool LoadUSDALayerFromMemory(const uint8_t *addr, const size_t length, + const std::string &asset_name, Layer *dst_layer, + std::string *warn, std::string *err, + const USDLoadOptions &options) { + + // TODO: options + (void)options; + + if (!addr) { + if (err) { + (*err) += "addr arg is nullptr.\n"; + } + return false; + } + + if (length < 9) { + if (err) { + (*err) += "Input too short.\n"; + } + return false; + } + + if (!dst_layer) { + if (err) { + (*err) += "dst_layher arg is nullptr.\n"; + } + return false; + } + + tinyusdz::StreamReader sr(addr, length, /* swap endian */ false); + tinyusdz::usda::USDAReader reader(&sr); + + tinyusdz::usda::USDAReaderConfig config; + config.strict_allowedToken_check = options.strict_allowedToken_check; + reader.set_reader_config(config); + + uint32_t load_states = static_cast(tinyusdz::LoadState::Toplevel); + + bool as_primspec = true; + + { + bool ret = reader.read(load_states, as_primspec); + + if (!ret) { + if (err) { + (*err) += "Failed to parse USDA: " + asset_name + "\n"; + (*err) += reader.get_error() + "\n"; + } + return false; + } + } + + tinyusdz::Layer layer; + bool ret = reader.get_as_layer(&layer); + if (!ret) { + if (err) { + (*err) += reader.get_error(); + } + return false; + } + + if (warn) { + if (reader.get_warning().size()) { + (*warn) += reader.get_warning(); + } + } + + (*dst_layer) = std::move(layer); + + return true; +} + +// Copy assetresolver state to all PrimSpec in the tree. +static bool PropagateAssetResolverState(uint32_t depth, PrimSpec &ps, + const std::string &cwp, + const std::vector &search_paths) { + if (depth > (1024 * 1024 * 512)) { + return false; + } + + if (depth == 0) { + DCOUT("current_working_path: " << cwp); + DCOUT("search_paths: " << search_paths); + } + + ps.set_asset_resolution_state(cwp, search_paths); + + for (auto &child : ps.children()) { + if (!PropagateAssetResolverState(depth + 1, child, cwp, search_paths)) { + return false; + } + } + + return true; +} + +bool LoadLayerFromMemory(const uint8_t *addr, const size_t length, + const std::string &asset_name, Layer *layer, + std::string *warn, std::string *err, + const USDLoadOptions &options) { + + bool ret{false}; + + if (IsUSDC(addr, length)) { + DCOUT("Detected as USDC."); +#if 1 + ret = LoadUSDCLayerFromMemory(addr, length, asset_name, layer, warn, err, + options); +#else + if (err) { + (*err) += "TODO: Load USDC as Layer is not implemented yet.\n"; + } + return false; +#endif + } else if (IsUSDA(addr, length)) { + DCOUT("Detected as USDA."); + ret = LoadUSDALayerFromMemory(addr, length, asset_name, layer, warn, err, + options); + } else if (IsUSDZ(addr, length)) { + DCOUT("Detected as USDZ."); +#if 0 + return LoadUSDZLayerFromMemory(addr, length, asset_name, layer, warn, err, + options); +#else + if (err) { + (*err) += "TODO: Load USDZ as Layer is not implemented yet.\n"; + } + return false; +#endif + } else { + if (err) { + (*err) += "Couldn't determine USD format(USDA/USDC/USDZ).\n"; + } + return false; + } + + if (ret) { + std::vector search_paths; // empty + std::string basedir = io::GetBaseDir(asset_name); + // Save current working path to each PrimSpec in the layer + // for the subsequent composition operation. + for (auto &root_ps : layer->primspecs()) { + PropagateAssetResolverState(0, root_ps.second, basedir, search_paths); + } + } + + return ret; +} + +bool LoadLayerFromFile(const std::string &_filename, Layer *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options) { + + if (_filename.empty()) { + PUSH_ERROR_AND_RETURN("Input filename is empty."); + } + + // TODO: Use AssetResolutionResolver. + std::string filepath = io::ExpandFilePath(_filename, /* userdata */ nullptr); + std::string base_dir = io::GetBaseDir(_filename); + + std::vector data; + size_t max_bytes = 1024 * 1024 * size_t(options.max_memory_limit_in_mb); + if (!io::ReadWholeFile(&data, err, filepath, max_bytes, + /* userdata */ nullptr)) { + return false; + } + + return LoadLayerFromMemory(data.data(), data.size(), filepath, stage, warn, err, + options); +} + +bool LoadLayerFromAsset(AssetResolutionResolver &resolver, const std::string &resolved_asset_name, Layer *layer, + std::string *warn, std::string *err, + const USDLoadOptions &options) { + + if (resolved_asset_name.empty()) { + PUSH_ERROR_AND_RETURN("Input asset name is empty."); + } + + resolver.set_max_asset_bytes_in_mb(options.max_allowed_asset_size_in_mb); + + Asset asset; + if (!resolver.open_asset(resolved_asset_name, resolved_asset_name, &asset, warn, err)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to open asset `{}`.", resolved_asset_name)); + } + + return LoadLayerFromMemory(asset.data(), asset.size(), resolved_asset_name, layer, warn, err, + options); +} + +int USDZResolveAsset(const char *asset_name, const std::vector &search_paths, std::string *resolved_asset_name, std::string *err, void *userdata) { + + DCOUT("Resolve asset: " << asset_name); + + if (!userdata) { + if (err) { + (*err) += "`userdata` must be non-null.\n"; + } + return -2; + } + + if (!asset_name) { + if (err) { + (*err) += "`asset_name` must be non-null.\n"; + } + return -2; + } + + if (!resolved_asset_name) { + if (err) { + (*err) += "`resolved_asset_name` must be non-null.\n"; + } + return -2; + } + + std::string asset_path = asset_name; + + // Remove relative path prefix './' + if (tinyusdz::startsWith(asset_path, "./")) { + asset_path = tinyusdz::removePrefix(asset_path, "./"); + } + + // Not used + (void)search_paths; + + const USDZAsset *passet = reinterpret_cast(userdata); + + if (passet->asset_map.count(asset_path)) { + DCOUT("Resolved asset: " << asset_name << " as " << asset_path); + (*resolved_asset_name) = asset_path; + return 0; + } + + return -1; // not found +} + +int USDZSizeAsset(const char *resolved_asset_name, uint64_t *nbytes, std::string *err, void *userdata) { + + if (!userdata) { + if (err) { + (*err) += "`userdata` must be non-null.\n"; + } + return -2; + } + + if (!resolved_asset_name) { + if (err) { + (*err) += "`resolved_asset_name` must be non-null.\n"; + } + return -2; + } + + if (!nbytes) { + if (err) { + (*err) += "`nbytes` must be non-null.\n"; + } + return -2; + } + + const USDZAsset *passet = reinterpret_cast(userdata); + + if (!passet->asset_map.count(resolved_asset_name)) { + if (err) { + (*err) += "resolved_asset_name `" + std::string(resolved_asset_name) + "` not found in USDZAsset.\n"; + } + return -1; + } + + std::pair byte_range = passet->asset_map.at(resolved_asset_name); + + if (byte_range.first >= byte_range.second) { + if (err) { + (*err) += "Invalid USDZAsset byte range.\n"; + } + return -2; + } + + (*nbytes) = byte_range.second - byte_range.first; + + return 0; +} + +int USDZReadAsset(const char *resolved_asset_name, uint64_t req_bytes, uint8_t *out_buf, uint64_t *nbytes, std::string *err, void *userdata) { + if (!userdata) { + if (err) { + (*err) += "`userdata` must be non-null.\n"; + } + return -1; + } + + if (!resolved_asset_name) { + if (err) { + (*err) += "`resolved_asset_name` must be non-null.\n"; + } + return -2; + } + + if (!out_buf) { + if (err) { + (*err) += "`out_buf` must be non-null.\n"; + } + return -2; + } + + if (!nbytes) { + if (err) { + (*err) += "`nbytes` must be non-null.\n"; + } + return -2; + } + + const USDZAsset *passet = reinterpret_cast(userdata); + + if (!passet->asset_map.count(resolved_asset_name)) { + if (err) { + (*err) += "resolved_asset_name `" + std::string(resolved_asset_name) + "` not found in USDZAsset.\n"; + } + return -1; + } + + std::pair byte_range = passet->asset_map.at(resolved_asset_name); + + if (byte_range.first >= byte_range.second) { + if (err) { + (*err) += "Invalid USDZAsset byte range.\n"; + } + return -2; + } + + size_t sz = byte_range.second - byte_range.first; + + if (sz > req_bytes) { + if (err) { + (*err) += "USDZAsset " + std::string(resolved_asset_name) + "'s size exceeds requested bytes.\n"; + } + return -2; + } + + if (byte_range.first + sz > passet->data.size()) { + if (err) { + (*err) += "Invalid USDZAsset size: " + std::string(resolved_asset_name) + "\n"; + } + return -2; + } + + memcpy(out_buf, passet->data.data() + byte_range.first, sz); + (*nbytes) = sz; + + return 0; +} + +bool SetupUSDZAssetResolution( + AssetResolutionResolver &resolver, + const USDZAsset *pusdzAsset) +{ + // https://openusd.org/release/spec_usdz.html + // + // [x] Image: png, jpeg(jpg), exr + // + // TODO(LTE): + // + // [ ] USD: usda, usdc, usd + // [ ] Audio: m4a, mp3, wav + + if (!pusdzAsset) { + return false; + } + // TODO: Validate Asset data. + + AssetResolutionHandler handler; + handler.resolve_fun = USDZResolveAsset; + handler.size_fun = USDZSizeAsset; + handler.read_fun = USDZReadAsset; + handler.write_fun = nullptr; + handler.userdata = reinterpret_cast(const_cast(pusdzAsset)); + + resolver.register_asset_resolution_handler("png", handler); + resolver.register_asset_resolution_handler("PNG", handler); + resolver.register_asset_resolution_handler("JPG", handler); + resolver.register_asset_resolution_handler("jpg", handler); + resolver.register_asset_resolution_handler("jpeg", handler); + resolver.register_asset_resolution_handler("JPEG", handler); + resolver.register_asset_resolution_handler("exr", handler); + resolver.register_asset_resolution_handler("EXR", handler); + + return true; +} + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tinyusdz.hh b/contrib/tinyusdz/tinyusdz_repo/src/tinyusdz.hh new file mode 100644 index 000000000..ff408c9ec --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tinyusdz.hh @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright (c) 2019 - 2023, Syoyo Fujita. +// Copyright (c) 2023 - Present, Light Transport Entertainment Inc. + +#ifndef TINYUSDZ_HH_ +#define TINYUSDZ_HH_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef TINYUSDZ_LOCAL_DEBUG_PRINT +#include // dbg +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +// TODO: Use std:: version for C++17 +#include "nonstd/expected.hpp" +#include "nonstd/optional.hpp" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include "image-types.hh" +#include "prim-types.hh" +#include "texture-types.hh" +#include "usdGeom.hh" +#include "usdLux.hh" +#include "usdShade.hh" +#include "usdSkel.hh" +//#include "usdVox.hh" +#include "stage.hh" +#include "asset-resolution.hh" + + +namespace tinyusdz { + +constexpr int version_major = 0; +constexpr int version_minor = 8; +constexpr int version_micro = 0; +constexpr auto version_rev = "rc5"; // extra revision suffix + +struct USDLoadOptions { + /// + /// Set the number of threads to use when parsing USD scene. + /// -1 = use # of system threads(CPU cores/threads). + /// + int num_threads{-1}; + + // Set the maximum memory limit advisorily(including image data). + // This feature would be helpful if you want to load USDZ model in mobile + // device. + int32_t max_memory_limit_in_mb{16384}; // in [mb] Default 16GB + + /// + /// TODO: Deprecate + /// Loads asset data(e.g. texture image, audio). Default is true. + /// If you want to load asset data in your own way or don't need asset data to + /// be loaded, Set this false. + /// + bool load_assets{true}; + + /// + /// (experimental) + /// Do composition on load(Load sublayers, references, etc) + /// For USDZ model, this should be false. + /// + bool do_composition{false}; + + /// + /// Following load flags are valid when `do_composition` is set `true`. + /// + bool load_sublayers{false}; // true: Load `subLayers` + bool load_references{false}; // true: Load `references` + bool load_payloads{false}; // true: Load `paylod` at top USD loading(no lazy loading). + + /// + /// Max MBs allowed for each asset file(e.g. jpeg) + /// + uint32_t max_allowed_asset_size_in_mb{1024}; // [mb] default 1GB. + + /// + /// For texture size + /// + uint32_t max_image_width = 2048; + uint32_t max_image_height = 2048; + uint32_t max_image_channels = 4; + + /// + /// For usdSkel + /// + bool strict_usdSkel_check{false}; // Strict usdSkel parsing check when true. + + /// + /// allowedToken + /// + bool strict_allowedToken_check{false}; // Make parse error when token value is not in allowedToken list(when the schema defines allowedToken list) + + /// + /// User-defined fileformat hander. + /// key = file(asset) extension(`.` excluded. example: 'mtlx', 'obj'). + /// + std::map fileformats; + + Axis upAxis{Axis::Y}; +}; + + +// TODO: Provide profiles for loader option. +// e.g. +// - Embedded(e.g. web, tigh resource size limit for security) +// - Realtime(moderate resource size limit) +// - DCC(for data conversion. Unlimited resource size) + +#if 0 // TODO +//struct USDWriteOptions +//{ +// +// +//}; +#endif + +// + +/// +/// Load USD(USDA/USDC/USDZ) from a file. +/// Automatically detect file format. +/// +/// @param[in] filename USD filename(UTF-8) +/// @param[out] stage USD stage(scene graph). +/// @param[out] warn Warning message. +/// @param[out] err Error message(filled when the function returns false) +/// @param[in] options Load options(optional) +/// +/// @return true upon success +/// +bool LoadUSDFromFile(const std::string &filename, Stage *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options = USDLoadOptions()); + +/// +/// Load USD(USDA/USDC/USDZ) from memory. +/// Automatically detect file format. +/// +/// @param[in] addr Memory address of USD data +/// @param[in] length Byte length of USD data +/// @param[in] filename Filename(can be empty). +/// @param[out] stage USD stage(scene graph). +/// @param[out] warn Warning message. +/// @param[out] err Error message(filled when the function returns false) +/// @param[in] options Load options(optional) +/// +/// @return true upon success +/// +bool LoadUSDFromMemory(const uint8_t *addr, const size_t length, + const std::string &filename, Stage *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options = USDLoadOptions()); + +/// +/// Load USDZ(zip) from a file. +/// It will load first USD file in USDZ container. +/// +/// @param[in] filename USDZ filename(UTF-8) +/// @param[out] stage USD stage(scene graph). +/// @param[out] warn Warning message. +/// @param[out] err Error message(filled when the function returns false) +/// @param[in] options Load options(optional) +/// +/// @return true upon success +/// +bool LoadUSDZFromFile(const std::string &filename, Stage *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options = USDLoadOptions()); + +#ifdef _WIN32 +// WideChar(Unicode filename) version +bool LoadUSDZFromFile(const std::wstring &filename, Stage *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options = USDLoadOptions()); +#endif + + +/// +/// Load USDZ(zip) from memory. +/// It will load first USD file in USDZ container. +/// +/// @param[in] addr Memory address of USDZ data +/// @param[in] length Byte length of USDZ data +/// @param[in] filename Filename(can be empty). +/// @param[out] stage USD stage(scene graph). +/// @param[out] warn Warning message. +/// @param[out] err Error message(filled when the function returns false) +/// @param[in] options Load options(optional) +/// +/// @return true upon success +/// +bool LoadUSDZFromMemory(const uint8_t *addr, const size_t length, + const std::string &filename, Stage *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options = USDLoadOptions()); + +struct USDZAsset +{ + // key: asset name(USD, Image, Audio, ...), value = byte begin/end in USDZ data. + std::map> asset_map; + + // When mmapped, `data` is empty, and `addr`(Usually pointer to mmaped address) and `size` are set. + // When non-mmapped, `data` holds the copy of whole USDZ data. + std::vector data; // USDZ itself + const uint8_t *addr{nullptr}; + size_t size{0}; // in bytes. + + bool is_mmaped() const { + return !data.empty(); + } +}; + +/// +/// Read USDZ(zip) asset info from a file. +/// +/// Whole file content(USDZ) is copied into USDZAsset::data. +/// If you want to save memory to load USDZ with assets, first read USDZ conent into memory(or Use io-util.hh::MMapFile() to mmap file), then use `ReadUSDZAssetInfoFromMemory with `assert_on_memory` true. +/// +/// @param[in] filename USDZ filename(UTF-8) +/// @param[out] asset USDZ asset info. +/// @param[out] warn Warning message. +/// @param[out] err Error message(filled when the function returns false) +/// @param[in] max_file_size_in_mb Maximum file size +/// +/// @return true upon success +/// +bool ReadUSDZAssetInfoFromFile(const std::string &filename, USDZAsset *asset, + std::string *warn, std::string *err, size_t max_file_size_in_mb = 16384ull); + +/// +/// Read USDZ(zip) asset info from memory. +/// +/// @param[in] addr Memory address +/// @param[in] asset_on_memory When true, do not copy USDZ data(`length` bytes from `addr` address) to USDZAsset. Instead just retain `addr` and `length` in USDZAsset. Memory address `addr` must be retained during any asset data in USDZAsset is accessed. When false, USDZ data is copied into USDZAsset. +/// +/// @param[out] asset USDZ asset info. +/// @param[out] warn Warning message. +/// @param[out] err Error message(filled when the function returns false) +/// +/// @return true upon success +/// +bool ReadUSDZAssetInfoFromMemory(const uint8_t *addr, const size_t length, const bool asset_on_memory, USDZAsset *asset, + std::string *warn, std::string *err); + +/// +/// Handy utility API to setup AssetResolutionResolver to load asset data from USDZ data. +/// +/// @param[inout] resolver Add asset resolution to the resolver. +/// @param[out] resolver Add asset resolution to the resolver. +/// @param[out] content data buffer(USDZ asset data). Must be retained until there is (potential) access to any asset, since AssetResolutionResolver and FileSystemHandler loads an asset from this buffer. +/// @param[in] overwrite_asset_info. Overwrite asset info when an asset info already exists in `resolver`? This could be useful if you provide your own custom asset loader for specific asset name(Add asset info before `SetupAssertResolutionForUSDZ` call and set `Overwrite` false). +/// @param[out] warn Warning message. +/// @param[out] err Error message. +/// +/// @return upon success and setup `resolver` and `fsHandler`. +/// +bool SetupUSDZAssetResolution( + AssetResolutionResolver &resolver, + const USDZAsset *pusdzAsset); + +/// +/// Default AssetResolution handler for USDZ(read asset from USDZ container) +/// +int USDZResolveAsset(const char *asset_name, const std::vector &search_paths, std::string *resolved_asset_name, std::string *err, void *userdata); +int USDZSizeAsset(const char *resolved_asset_name, uint64_t *nbytes, std::string *err, void *userdata); +int USDZReadAsset(const char *resolved_asset_name, uint64_t req_bytes, uint8_t *out_buf, uint64_t *nbytes, std::string *err, void *userdata); + +/// +/// Load USDC(binary) from a file. +/// +/// @param[in] filename USDC filename(UTF-8) +/// @param[out] stage USD stage(scene graph). +/// @param[out] warn Warning message. +/// @param[out] err Error message(filled when the function returns false) +/// @param[in] options Load options(optional) +/// +/// @return true upon success +/// +bool LoadUSDCFromFile(const std::string &filename, Stage *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options = USDLoadOptions()); + +/// +/// Load USDC(binary) from a memory. +/// +/// @param[in] addr Memory address of USDC data +/// @param[in] length Byte length of USDC data +/// @param[in] filename Filename(can be empty). +/// @param[out] stage USD stage. +/// @param[out] warn Warning message. +/// @param[out] err Error message(filled when the function returns false) +/// @param[in] options Load options(optional) +/// +/// @return true upon success +/// +bool LoadUSDCFromMemory(const uint8_t *addr, const size_t length, + const std::string &filename, Stage *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options = USDLoadOptions()); + +/// +/// Load USDA(ascii) from a file. +/// +/// @param[in] filename USDA filename(UTF-8) +/// @param[out] stage USD stage. +/// @param[out] warn Warning message. +/// @param[out] err Error message(filled when the function returns false) +/// @param[in] options Load options(optional) +/// +/// @return true upon success +/// +bool LoadUSDAFromFile(const std::string &filename, Stage *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options = USDLoadOptions()); + +/// +/// Load USDA(ascii) from a memory. +/// +/// @param[in] addr Memory address of USDA data +/// @param[in] length Byte length of USDA data +/// @param[in[ base_dir Base directory(can be empty) +/// @param[out] stage USD stage. +/// @param[out] warn Warning message. +/// @param[out] err Error message(filled when the function returns false) +/// @param[in] options Load options(optional) +/// +/// @return true upon success +/// +bool LoadUSDAFromMemory(const uint8_t *addr, const size_t length, + const std::string &base_dir, Stage *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options = USDLoadOptions()); + +/// +/// For composition +/// + +/// +/// Load USD(USDA/USDC/USDZ) from a file and return it as Layer. +/// Automatically detect file format. +/// +/// @param[in] filename USD filename(UTF-8) +/// @param[out] layer USD layer(scene graph). +/// @param[out] warn Warning message. +/// @param[out] err Error message(filled when the function returns false) +/// @param[in] options Load options(optional) +/// +/// @return true upon success +/// +bool LoadLayerFromFile(const std::string &filename, Layer *stage, + std::string *warn, std::string *err, + const USDLoadOptions &options = USDLoadOptions()); + +/// +/// Load USD(USDA/USDC/USDZ) from memory and return it as Layer. +/// Automatically detect file format. +/// +/// @param[in] addr Memory address of USD data +/// @param[in] length Byte length of USD data +/// @param[in] filename Corresponding Filename(can be empty). +/// @param[out] layer USD layer(scene graph). +/// @param[out] warn Warning message. +/// @param[out] err Error message(filled when the function returns false) +/// @param[in] options Load options(optional) +/// +/// @return true upon success +/// +bool LoadLayerFromMemory(const uint8_t *addr, const size_t length, + const std::string &filename, Layer *layer, + std::string *warn, std::string *err, + const USDLoadOptions &options = USDLoadOptions()); + + +bool LoadUSDALayerFromMemory(const uint8_t *addr, const size_t length, + const std::string &filename, Layer *layer, + std::string *warn, std::string *err, + const USDLoadOptions &options = USDLoadOptions()); + +bool LoadUSDCLayerFromMemory(const uint8_t *addr, const size_t length, + const std::string &filename, Layer *layer, + std::string *warn, std::string *err, + const USDLoadOptions &options = USDLoadOptions()); + +/// +/// Load USD(USDA/USDC/USDZ) layer using AssetResolution resolver. +/// This API would be useful if you want to load USD from custom storage(e.g, on Android), URI(web), DB, etc. +/// Automatically detect file format. +/// +/// resolved_asset_name must be resolved asset name using AssetResolutionResolver::resolve() +/// +/// @param[in] resolver AssetResolution resolver. +/// @param[in] resolved_asset_name Resolved asset name. +/// @param[out] layer USD layer(scene graph). +/// @param[out] warn Warning message. +/// @param[out] err Error message(filled when the function returns false) +/// @param[in] options Load options(optional) +/// +/// @return true upon success +/// +bool LoadLayerFromAsset(AssetResolutionResolver &resolver, + const std::string &resolved_asset_name, Layer *layer, + std::string *warn, std::string *err, + const USDLoadOptions &options = USDLoadOptions()); + +#if 0 // TODO +bool LoadUSDZLayerFromMemory(const uint8_t *addr, const size_t length, + const std::string &filename, Layer *layer, + std::string *warn, std::string *err, + const USDLoadOptions &options = USDLoadOptions()); +#endif + +#if 0 // TODO +/// +/// Write stage as USDC to a file. +/// +/// @param[in] filename USDC filename +/// @param[out] err Error message(filled when the function returns false) +/// @param[in] options Write options(optional) +/// +/// @return true upon success +/// +bool WriteAsUSDCToFile(const std::string &filename, std::string *err, const USDCWriteOptions &options = USDCWriteOptions()); + +#endif + +// Test if input is any of USDA/USDC/USDZ format. +// Optionally returns detected format("usda", "usdc", or "usdz") to +// `detected_format` when a given file/binary is a USD format. +bool IsUSD(const std::string &filename, std::string *detected_format = nullptr); +bool IsUSD(const uint8_t *addr, const size_t length, + std::string *detected_format = nullptr); + +// Test if input is USDA format. +bool IsUSDA(const std::string &filename); +bool IsUSDA(const uint8_t *addr, const size_t length); + +// Test if input is USDC(Crate binary) format. +bool IsUSDC(const std::string &filename); +bool IsUSDC(const uint8_t *addr, const size_t length); + +// Test if input is USDZ(Uncompressed ZIP) format. +bool IsUSDZ(const std::string &filename); +bool IsUSDZ(const uint8_t *addr, const size_t length); + +} // namespace tinyusdz + +#endif // TINYUSDZ_HH_ diff --git a/contrib/tinyusdz/tinyusdz_repo/src/token-type.hh b/contrib/tinyusdz/tinyusdz_repo/src/token-type.hh new file mode 100644 index 000000000..5bcdee07c --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/token-type.hh @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: Apache 2.0 +// `token` type +#pragma once + +// +// a class for `token` type. +// +// `token` is primarily used for a short-length string. +// +// Unlike pxrUSD, `Token` class does not acquire a lock by default. This means +// there is a potential hash collision for the hash value of `Token` string, but +// TinyUSDZ does not require token(string) hashes are unique inside of TinyUSDZ +// library. If you need pxrUSD-like behavior of `Token` class(i.e, you want a +// token hash with no collision), you can compile TinyUSDZ with +// TINYUSDZ_USE_STRING_ID_FOR_TOKEN_TYPE. +// (Also you need to include foonathan/string_id c++ files(Please see /CMakeLists.txt) to your project) +// +// TINYUSDZ_USE_STRING_ID_FOR_TOKEN_TYPE +// - Use foonathan/string_id to implement Token class. +// - database(token storage) is accessed with mutex so an application should +// not frequently construct Token class among threads. +// +// --- +// +// + +#include +#include + +#include "nonstd/optional.hpp" + +#if defined(TINYUSDZ_USE_STRING_ID_FOR_TOKEN_TYPE) + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +// external +#include "external/string_id/database.hpp" +#include "external/string_id/string_id.hpp" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#else // TINYUSDZ_USE_STRING_ID_FOR_TOKEN_TYPE +#include +#endif // TINYUSDZ_USE_STRING_ID_FOR_TOKEN_TYPE + +namespace tinyusdz { + +#if defined(TINYUSDZ_USE_STRING_ID_FOR_TOKEN_TYPE) + +namespace sid = foonathan::string_id; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +// Singleton +class TokenStorage { + public: + TokenStorage(const TokenStorage &) = delete; + TokenStorage &operator=(const TokenStorage &) = delete; + TokenStorage(TokenStorage &&) = delete; + TokenStorage &operator=(TokenStorage &&) = delete; + + static sid::default_database &GetInstance() { + static sid::default_database s_database; + return s_database; + } + + private: + TokenStorage() = default; + ~TokenStorage() = default; +}; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +class Token { + public: + Token() {} + + explicit Token(const std::string &str) { + str_ = sid::string_id(str.c_str(), TokenStorage::GetInstance()); + } + + explicit Token(const char *str) { + str_ = sid::string_id(str, TokenStorage::GetInstance()); + } + + const std::string str() const { + if (!str_) { + return std::string(); + } + return str_.value().string(); + } + + uint64_t hash() const { + if (!str_) { + return 0; + } + + // Assume non-zero hash value for non-empty string. + return str_.value().hash_code(); + } + + bool valid() const { + if (!str_) { + return false; + } + + if (str_.value().string().empty()) { + return false; + } + + return true; + } + + private: + nonstd::optional str_; +}; + +inline bool operator==(const Token &tok, const std::string &rhs) { + return tok.str().compare(rhs) == 0; +} + +struct TokenHasher { + inline size_t operator()(const Token &tok) const { + return size_t(tok.hash()); + } +}; + +struct TokenKeyEqual { + bool operator()(const Token &lhs, const Token &rhs) const { + return lhs.str() == rhs.str(); + } +}; + +#else // TINYUSDZ_USE_STRING_ID_FOR_TOKEN_TYPE + +class Token { + public: + Token() {} + + explicit Token(const std::string &str) { str_ = str; } + + explicit Token(const char *str) { str_ = str; } + + const std::string &str() const { return str_; } + + bool valid() const { + if (str().empty()) { + return false; + } + + return true; + } + + // No string hash for TinyUSDZ +#if 0 + uint64_t hash() const { + if (!str_) { + return 0; + } + + // Assume non-zero hash value for non-empty string. + return str_.value().hash_code(); + } +#endif + + private: + std::string str_; +}; + +struct TokenHasher { + inline size_t operator()(const Token &tok) const { + return std::hash()(tok.str()); + } +}; + +struct TokenKeyEqual { + bool operator()(const Token &lhs, const Token &rhs) const { + return lhs.str() == rhs.str(); + } +}; + +#endif // TINYUSDZ_USE_STRING_ID_FOR_TOKEN_TYPE + +inline bool operator==(const Token &lhs, const Token &rhs) { + return TokenKeyEqual()(lhs, rhs); +} + +inline bool operator!=(const Token &lhs, const Token &rhs) { + return !TokenKeyEqual()(lhs, rhs); +} + +inline bool operator<(const Token &lhs, const Token &rhs) { + return lhs.str() < rhs.str(); +} + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/README.md b/contrib/tinyusdz/tinyusdz_repo/src/tydra/README.md new file mode 100644 index 000000000..fd74a773e --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/README.md @@ -0,0 +1,50 @@ +# What is Tydra? + +![Tydra](tydra.png) + +TinyUSDZ does not support Hydra interface in pxrUSD at the moment. +We think Hydra(multi-purpose sceneDelegate/renderDelegate interface) is too much for TinyUSDZ usecases(AR, lightweight 3D viewer/runtime, DCC exchange, etc). + +Instead, we'd like to propose Tydra(Tiny Hydra), something like a three-headed monster(Please imagine Gidorah: https://en.wikipedia.org/wiki/King_Ghidorah), which directly converts(`publishes`) USD scene graph(Stage. Prim hierarchy) to a renderer-friendly data structure or `published` data format(imagine glTF). API design of Tydra is completely different from Hydra. + +Currently Tydra is considering following three usecases in mind: + +- Runtime publishment(e.g. to glTF), DCC conversion and exchange for realtime graphics(AR, VR, MR, games, etc). +- Scene conversion to GL/Vulkan renderer(e.g. WebGL rendering) +- Scene conversion to Ray tracing renderer(e.g. Vulkan/OptiX ray tracing) + See `../../examples/sdlviewer/` for SW raytracing example. + +## Status + +Work in progres. API and feature is subject to change. + +## RenderScene + +Scene graph representation suited for OpenGL/Vulkan renderer. + +### Status + +* [ ] Node xform +* [x] Triangulate mesh +* [ ] Subdivision surface support(subdivide mesh using OpenSubdiv) +* [x] Resolve Material binding + * [x] GeomSubset material binding + * [ ] Collection material binding +* [ ] Load and setup Texture + * Colorspace conversion + * [x] sRGB <-> Linear + * [x] rec709 <-> Linear + * [ ] OCIO LUT + * +* [ ] Skinning +* [ ] BlendShape +* [ ] Animation +* [ ] Lights + +## TODO + +- Data structure suited for realtime DCC. +- Data structure suited for Ray tracing + +EoL. + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval-typed-animatable-fallback.cc b/contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval-typed-animatable-fallback.cc new file mode 100644 index 000000000..2f3e3061d --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval-typed-animatable-fallback.cc @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022-Present Light Transport Entertainment, Inc. +// +#include "attribute-eval.hh" +#include "scene-access.hh" + +#include "common-macros.inc" +#include "pprinter.hh" +#include "tiny-format.hh" +#include "value-pprint.hh" + +namespace tinyusdz { +namespace tydra { + +// For PUSH_ERROR_AND_RETURN +#define PushError(msg) \ + if (err) { \ + (*err) += msg; \ + } + + +template +bool EvaluateTypedAttributeImpl( + const tinyusdz::Stage &stage, const TypedAttributeWithFallback> &attr, + const std::string &attr_name, + T *value, + std::string *err, + const double t, const value::TimeSampleInterpolationType tinterp) +{ + + if (attr.is_connection()) { + // Follow connection target Path(singple targetPath only). + std::vector pv = attr.connections(); + if (pv.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Connection targetPath is empty for Attribute {}.", attr_name)); + } + + if (pv.size() > 1) { + PUSH_ERROR_AND_RETURN( + fmt::format("Multiple targetPaths assigned to .connection for Attribute {}.", attr_name)); + } + + auto target = pv[0]; + + std::string targetPrimPath = target.prim_part(); + std::string targetPrimPropName = target.prop_part(); + DCOUT("connection targetPath : " << target << "(Prim: " << targetPrimPath + << ", Prop: " << targetPrimPropName + << ")"); + + auto targetPrimRet = + stage.GetPrimAtPath(Path(targetPrimPath, /* prop */ "")); + if (targetPrimRet) { + // Follow the connetion + const Prim *targetPrim = targetPrimRet.value(); + + std::string abs_path = target.full_path_name(); + + TerminalAttributeValue attr_value; + + bool ret = EvaluateAttribute(stage, *targetPrim, targetPrimPropName, + &attr_value, err, t, tinterp); + + if (!ret) { + return false; + } + + if (const auto pav = attr_value.as()) { + (*value) = (*pav); + return true; + } else { + PUSH_ERROR_AND_RETURN( + fmt::format("Attribute of Connection targetPath has different type `{}. Expected `{}`. Attribute `{}`.", attr_value.type_name(), value::TypeTraits::type_name(), attr_name)); + } + + + } else { + PUSH_ERROR_AND_RETURN(targetPrimRet.error()); + } + } else if (attr.is_blocked()) { + PUSH_ERROR_AND_RETURN( + fmt::format("Attribute `{}` is ValueBlocked(None).", attr_name)); + } else { + + return attr.get_value(value); + + } + + return false; +} + + +namespace { + +// Convert TypedAttribute Connection to Attribute Connection. +// If TypedAttribute has value, return Attribute with empty value. +// TODO: make error when Attribute is not 'connection'. +template +Attribute ToAttributeConnection( + const TypedAttributeWithFallback> &input) +{ + Attribute attr; + if (input.is_blocked()) { + attr.set_blocked(true); + attr.variability() = Variability::Varying; + } else if (input.is_value_empty()) { + // empty = set type info only + attr.set_type_name(value::TypeTraits::type_name()); + attr.variability() = Variability::Varying; + + } else if (input.is_connection()) { + + attr.set_connections(input.connections()); + + } else{ + attr.set_type_name(value::TypeTraits::type_name()); + attr.variability() = Variability::Varying; + } + + return attr; +} + +} // namespace + +template +bool EvaluateTypedAnimatableAttribute( + const tinyusdz::Stage &stage, const TypedAttributeWithFallback> &tattr, + const std::string &attr_name, + T *value_out, + std::string *err, + const double t, + const value::TimeSampleInterpolationType tinterp) { + + if (!value_out) { + PUSH_ERROR_AND_RETURN("`value_out` param is nullptr."); + } + + if (tattr.is_blocked()) { + if (err) { + (*err) += "Attribute is Blocked.\n"; + } + return false; + } else if (tattr.is_value_empty()) { + if (err) { + (*err) += "Attribute value is empty.\n"; + } + return false; + } else if (tattr.is_connection()) { + + // Follow targetPath + Attribute attr = ToAttributeConnection(tattr); + + //std::set visited_paths; + + TerminalAttributeValue value; + bool ret = EvaluateAttribute(stage, attr, attr_name, &value, err, + value::TimeCode::Default(), value::TimeSampleInterpolationType::Held); + + if (!ret) { + return false; + } + + if (auto pv = value.as()) { + (*value_out) = *pv; + return true; + } + + if (err) { + (*err) += fmt::format("Type mismatch. Value producing attribute has type {}, but requested type is {}[]. Attribute: {}", value.type_name(), value::TypeTraits::type_name(), attr_name); + } + + } else { + const Animatable &value = tattr.get_value(); + T v; + if (value.get(t, &v, tinterp)) { + return true; + } else { + if (err) { + (*err) += fmt::format("Failed to get TypedAnimatableAttribute value: {} \n", attr_name); + } + return false; + } + } + return false; +} + +template<> +bool EvaluateTypedAnimatableAttribute( + const tinyusdz::Stage &stage, const TypedAttributeWithFallback> &tattr, + const std::string &attr_name, + std::string *value_out, + std::string *err, + const double t, + const value::TimeSampleInterpolationType tinterp) { + + if (!value_out) { + PUSH_ERROR_AND_RETURN("`value_out` param is nullptr."); + } + + if (tattr.is_blocked()) { + if (err) { + (*err) += "Attribute is Blocked.\n"; + } + return false; + } else if (tattr.is_value_empty()) { + if (err) { + (*err) += "Attribute value is empty.\n"; + } + return false; + } else if (tattr.is_connection()) { + + // Follow targetPath + Attribute attr = ToAttributeConnection(tattr); + + //std::set visited_paths; + + TerminalAttributeValue value; + bool ret = EvaluateAttribute(stage, attr, attr_name, &value, err, + value::TimeCode::Default(), value::TimeSampleInterpolationType::Held); + + if (!ret) { + return false; + } + + if (auto pv = value.as()) { + (*value_out) = *pv; + return true; + } + + // Allow `token` typed value in the attribute of targetPath. + if (auto pv = value.as()) { + // TODO: report an warninig. + (*value_out) = pv->str(); + return true; + } + + if (err) { + (*err) += fmt::format("Type mismatch. Value producing attribute has type {}, but requested type is {}[]. Attribute: {}", value.type_name(), value::TypeTraits::type_name(), attr_name); + } + + } else { + const Animatable &value = tattr.get_value(); + std::string v; + if (value.get(t, &v, tinterp)) { + return true; + } else { + if (err) { + (*err) += fmt::format("Failed to get TypedAnimatableAttribute value: {} \n", attr_name); + } + return false; + } + + } + return false; +} + +// template instanciations +#define EVALUATE_TYPED_ATTRIBUTE_INSTANCIATE(__ty) \ +template bool EvaluateTypedAnimatableAttribute(const tinyusdz::Stage &stage, const TypedAttributeWithFallback> &attr, const std::string &attr_name, __ty *value, std::string *err, const double t, const value::TimeSampleInterpolationType tinterp); + +APPLY_FUNC_TO_VALUE_TYPES_NO_STRING(EVALUATE_TYPED_ATTRIBUTE_INSTANCIATE) + +#undef EVALUATE_TYPED_ATTRIBUTE_INSTANCIATE + + +} // namespace tydra +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval-typed-animatable.cc b/contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval-typed-animatable.cc new file mode 100644 index 000000000..808eab848 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval-typed-animatable.cc @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022-Present Light Transport Entertainment, Inc. +// +#include "attribute-eval.hh" +#include "scene-access.hh" + +#include "common-macros.inc" +#include "pprinter.hh" +#include "tiny-format.hh" +#include "value-pprint.hh" + +namespace tinyusdz { +namespace tydra { + +// For PUSH_ERROR_AND_RETURN +#define PushError(msg) \ + if (err) { \ + (*err) += msg; \ + } + + +template +bool EvaluateTypedAttributeImpl( + const tinyusdz::Stage &stage, const TypedAttribute> &attr, + const std::string &attr_name, + T *value, + std::string *err, + const double t, const value::TimeSampleInterpolationType tinterp) +{ + + if (attr.is_connection()) { + // Follow connection target Path(singple targetPath only). + std::vector pv = attr.connections(); + if (pv.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Connection targetPath is empty for Attribute {}.", attr_name)); + } + + if (pv.size() > 1) { + PUSH_ERROR_AND_RETURN( + fmt::format("Multiple targetPaths assigned to .connection for Attribute {}.", attr_name)); + } + + auto target = pv[0]; + + std::string targetPrimPath = target.prim_part(); + std::string targetPrimPropName = target.prop_part(); + DCOUT("connection targetPath : " << target << "(Prim: " << targetPrimPath + << ", Prop: " << targetPrimPropName + << ")"); + + auto targetPrimRet = + stage.GetPrimAtPath(Path(targetPrimPath, /* prop */ "")); + if (targetPrimRet) { + // Follow the connetion + const Prim *targetPrim = targetPrimRet.value(); + + std::string abs_path = target.full_path_name(); + + TerminalAttributeValue attr_value; + + bool ret = EvaluateAttribute(stage, *targetPrim, targetPrimPropName, + &attr_value, err, t, tinterp); + + if (!ret) { + return false; + } + + if (const auto pav = attr_value.as()) { + (*value) = (*pav); + return true; + } else { + PUSH_ERROR_AND_RETURN( + fmt::format("Attribute of Connection targetPath has different type `{}. Expected `{}`. Attribute `{}`.", attr_value.type_name(), value::TypeTraits::type_name(), attr_name)); + } + + + } else { + PUSH_ERROR_AND_RETURN(targetPrimRet.error()); + } + } else if (attr.is_blocked()) { + PUSH_ERROR_AND_RETURN( + fmt::format("Attribute `{}` is ValueBlocked(None).", attr_name)); + } else { + + return attr.get_value(value); + + } + + return false; +} + + +namespace { + +// Convert TypedAttribute Connection to Attribute Connection. +// If TypedAttribute has value, return Attribute with empty value. +// TODO: make error when Attribute is not 'connection'. +template +Attribute ToAttributeConnection( + const TypedAttribute> &input) +{ + Attribute attr; + if (input.is_blocked()) { + attr.set_blocked(true); + attr.variability() = Variability::Varying; + } else if (input.is_value_empty()) { + // empty = set type info only + attr.set_type_name(value::TypeTraits::type_name()); + attr.variability() = Variability::Varying; + + } else if (input.is_connection()) { + + attr.set_connections(input.connections()); + + } else{ + attr.set_type_name(value::TypeTraits::type_name()); + attr.variability() = Variability::Varying; + } + + return attr; +} + +} // namespace + +template<> bool EvaluateTypedAnimatableAttribute( + const tinyusdz::Stage &stage, const TypedAttribute> &tattr, + const std::string &attr_name, + std::string *value_out, + std::string *err, + const double t, + const value::TimeSampleInterpolationType tinterp) { + + if (!value_out) { + PUSH_ERROR_AND_RETURN("`value_out` param is nullptr."); + } + + if (tattr.is_blocked()) { + if (err) { + (*err) += "Attribute is Blocked.\n"; + } + return false; + } else if (tattr.is_value_empty()) { + if (err) { + (*err) += "Attribute value is empty.\n"; + } + return false; + } else if (tattr.is_connection()) { + + // Follow targetPath + Attribute attr = ToAttributeConnection(tattr); + + TerminalAttributeValue value; + bool ret = EvaluateAttribute(stage, attr, attr_name, &value, err, + t, tinterp); + + if (!ret) { + return false; + } + + if (auto pv = value.as()) { + (*value_out) = *pv; + return true; + } + + // Allow `token` typed value in the attribute of targetPath. + if (auto pv = value.as()) { + (*value_out) = pv->str(); + return true; + } + + if (err) { + (*err) += fmt::format("Type mismatch. Value producing attribute has type {}, but requested type is {}[]. Attribute: {}", value.type_name(), value::TypeTraits::type_name(), attr_name); + } + + } else { + Animatable value; + if (tattr.get_value(&value)) { + if (value.get(t, value_out, tinterp)) { + return true; + } else { + if (err) { + (*err) += fmt::format("Failed to get TypedAnimatableAttribute value: {} \n", attr_name); + } + return false; + } + } + + if (err) { + (*err) += fmt::format("[Internal error] Invalid TypedAttribute? : {} \n", attr_name); + } + } + return false; +} + +template +bool EvaluateTypedAnimatableAttribute( + const tinyusdz::Stage &stage, const TypedAttribute> &tattr, + const std::string &attr_name, + T *value_out, + std::string *err, + const double t, + const value::TimeSampleInterpolationType tinterp) { + + if (!value_out) { + PUSH_ERROR_AND_RETURN("`value_out` param is nullptr."); + } + + if (tattr.is_blocked()) { + if (err) { + (*err) += "Attribute is Blocked.\n"; + } + return false; + } else if (tattr.is_value_empty()) { + if (err) { + (*err) += "Attribute value is empty.\n"; + } + return false; + } else if (tattr.is_connection()) { + + // Follow targetPath + Attribute attr = ToAttributeConnection(tattr); + + TerminalAttributeValue value; + bool ret = EvaluateAttribute(stage, attr, attr_name, &value, err, + t, tinterp); + + if (!ret) { + return false; + } + + if (auto pv = value.as()) { + (*value_out) = *pv; + return true; + } + + if (err) { + (*err) += fmt::format("Type mismatch. Value producing attribute has type {}, but requested type is {}[]. Attribute: {}", value.type_name(), value::TypeTraits::type_name(), attr_name); + } + + } else { + Animatable value; + if (tattr.get_value(&value)) { + if (value.get(t, value_out, tinterp)) { + return true; + } else { + if (err) { + (*err) += fmt::format("Failed to get TypedAnimatableAttribute value: {} \n", attr_name); + } + return false; + } + } + + if (err) { + (*err) += fmt::format("[Internal error] Invalid TypedAttribute? : {} \n", attr_name); + } + } + return false; +} + + +// template instanciations +#define EVALUATE_TYPED_ATTRIBUTE_INSTANCIATE(__ty) \ +template bool EvaluateTypedAnimatableAttribute(const tinyusdz::Stage &stage, const TypedAttribute> &attr, const std::string &attr_name, __ty *value, std::string *err, const double t, const value::TimeSampleInterpolationType tinterp); + +APPLY_FUNC_TO_VALUE_TYPES_NO_STRING(EVALUATE_TYPED_ATTRIBUTE_INSTANCIATE) + +#undef EVALUATE_TYPED_ATTRIBUTE_INSTANCIATE + + + +} // namespace tydra +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval-typed-fallback.cc b/contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval-typed-fallback.cc new file mode 100644 index 000000000..fe9bf3873 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval-typed-fallback.cc @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022-Present Light Transport Entertainment, Inc. +// +#include "attribute-eval.hh" +#include "scene-access.hh" + +#include "common-macros.inc" +#include "pprinter.hh" +#include "tiny-format.hh" +#include "value-pprint.hh" + +namespace tinyusdz { +namespace tydra { + +// For PUSH_ERROR_AND_RETURN +#define PushError(msg) \ + if (err) { \ + (*err) += msg; \ + } + + +// +// visited_paths : To prevent circular referencing of attribute connection. +// +template +bool EvaluateTypedAttributeImpl( + const tinyusdz::Stage &stage, const TypedAttributeWithFallback &attr, + const std::string &attr_name, + T *value, + std::string *err, + const double t, const value::TimeSampleInterpolationType tinterp) +{ + + if (attr.is_connection()) { + // Follow connection target Path(singple targetPath only). + std::vector pv = attr.connections(); + if (pv.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Connection targetPath is empty for Attribute {}.", attr_name)); + } + + if (pv.size() > 1) { + PUSH_ERROR_AND_RETURN( + fmt::format("Multiple targetPaths assigned to .connection for Attribute {}.", attr_name)); + } + + auto target = pv[0]; + + std::string targetPrimPath = target.prim_part(); + std::string targetPrimPropName = target.prop_part(); + DCOUT("connection targetPath : " << target << "(Prim: " << targetPrimPath + << ", Prop: " << targetPrimPropName + << ")"); + + auto targetPrimRet = + stage.GetPrimAtPath(Path(targetPrimPath, /* prop */ "")); + if (targetPrimRet) { + // Follow the connetion + const Prim *targetPrim = targetPrimRet.value(); + + std::string abs_path = target.full_path_name(); + + TerminalAttributeValue attr_value; + + bool ret = EvaluateAttribute(stage, *targetPrim, targetPrimPropName, + &attr_value, err, t, tinterp); + + if (!ret) { + return false; + } + + if (const auto pav = attr_value.as()) { + (*value) = (*pav); + return true; + } else { + PUSH_ERROR_AND_RETURN( + fmt::format("Attribute of Connection targetPath has different type `{}. Expected `{}`. Attribute `{}`.", attr_value.type_name(), value::TypeTraits::type_name(), attr_name)); + } + + + } else { + PUSH_ERROR_AND_RETURN(targetPrimRet.error()); + } + } else if (attr.is_blocked()) { + PUSH_ERROR_AND_RETURN( + fmt::format("Attribute `{}` is ValueBlocked(None).", attr_name)); + } else { + + return attr.get_value(value); + + } + + return false; +} + + +namespace { + +// Convert TypedAttribute Connection to Attribute Connection. +// If TypedAttribute has value, return Attribute with empty value. +// TODO: make error when Attribute is not 'connection'. +template +Attribute ToAttributeConnection( + const TypedAttributeWithFallback &input) +{ + Attribute attr; + if (input.is_blocked()) { + attr.set_blocked(true); + attr.variability() = Variability::Uniform; + } else if (input.is_value_empty()) { + // empty = set type info only + attr.set_type_name(input.type_name()); + attr.variability() = Variability::Uniform; + + } else if (input.is_connection()) { + + attr.set_connections(input.connections()); + + } else{ + attr.set_type_name(input.type_name()); + attr.variability() = Variability::Uniform; + } + + return attr; +} + +} // namespace + +template +bool EvaluateTypedAttribute( + const tinyusdz::Stage &stage, const TypedAttributeWithFallback &tattr, + const std::string &attr_name, + T *value_out, + std::string *err) { + + if (!value_out) { + PUSH_ERROR_AND_RETURN("`value_out` param is nullptr."); + } + + if (tattr.is_blocked()) { + if (err) { + (*err) += "Attribute is Blocked.\n"; + } + return false; + } else if (tattr.is_value_empty()) { + if (err) { + (*err) += "Attribute value is empty.\n"; + } + return false; + } else if (tattr.is_connection()) { + + // Follow targetPath + Attribute attr = ToAttributeConnection(tattr); + + //std::set visited_paths; + + TerminalAttributeValue value; + bool ret = EvaluateAttribute(stage, attr, attr_name, &value, err, + value::TimeCode::Default(), value::TimeSampleInterpolationType::Held); + + if (!ret) { + return false; + } + + if (auto pv = value.as()) { + (*value_out) = *pv; + return true; + } + + if (err) { + (*err) += fmt::format("Type mismatch. Value producing attribute has type {}, but requested type is {}. Attribute: {}", value.type_name(), tattr.type_name(), attr_name); + } + + } else { + (*value_out) = tattr.get_value(); + return true; + } + return false; +} + +template<> +bool EvaluateTypedAttribute( + const tinyusdz::Stage &stage, const TypedAttributeWithFallback &tattr, + const std::string &attr_name, + std::string *value_out, + std::string *err) { + + if (!value_out) { + PUSH_ERROR_AND_RETURN("`value_out` param is nullptr."); + } + + if (tattr.is_blocked()) { + if (err) { + (*err) += "Attribute is Blocked.\n"; + } + return false; + } else if (tattr.is_value_empty()) { + if (err) { + (*err) += "Attribute value is empty.\n"; + } + return false; + } else if (tattr.is_connection()) { + + // Follow targetPath + Attribute attr = ToAttributeConnection(tattr); + + //std::set visited_paths; + + TerminalAttributeValue value; + bool ret = EvaluateAttribute(stage, attr, attr_name, &value, err, + value::TimeCode::Default(), value::TimeSampleInterpolationType::Held); + + if (!ret) { + return false; + } + + if (auto pv = value.as()) { + (*value_out) = *pv; + return true; + } + + if (auto pv = value.as()) { + (*value_out) = pv->str(); + return true; + } + + if (err) { + (*err) += fmt::format("Type mismatch. Value producing attribute has type {}, but requested type is {}. Attribute: {}", value.type_name(), tattr.type_name(), attr_name); + } + + } else { + (*value_out) = tattr.get_value(); + return true; + } + return false; +} + +// template instanciations +#define EVALUATE_TYPED_ATTRIBUTE_INSTANCIATE(__ty) \ +template bool EvaluateTypedAttribute(const tinyusdz::Stage &stage, const TypedAttributeWithFallback<__ty> &attr, const std::string &attr_name, __ty *value, std::string *err); + +APPLY_FUNC_TO_VALUE_TYPES_NO_STRING(EVALUATE_TYPED_ATTRIBUTE_INSTANCIATE) + +#undef EVALUATE_TYPED_ATTRIBUTE_INSTANCIATE + + +} // namespace tydra +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval-typed.cc b/contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval-typed.cc new file mode 100644 index 000000000..5a3de9775 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval-typed.cc @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022-Present Light Transport Entertainment, Inc. +// +#include "attribute-eval.hh" +#include "scene-access.hh" + +#include "common-macros.inc" +#include "pprinter.hh" +#include "tiny-format.hh" +#include "value-pprint.hh" + +namespace tinyusdz { +namespace tydra { + +// For PUSH_ERROR_AND_RETURN +#define PushError(msg) \ + if (err) { \ + (*err) += msg; \ + } + + +// +// visited_paths : To prevent circular referencing of attribute connection. +// +template +bool EvaluateTypedAttributeImpl( + const tinyusdz::Stage &stage, const TypedAttribute &attr, + const std::string &attr_name, + T *value, + std::string *err, + const double t, const value::TimeSampleInterpolationType tinterp) +{ + + if (attr.is_connection()) { + // Follow connection target Path(singple targetPath only). + std::vector pv = attr.connections(); + if (pv.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Connection targetPath is empty for Attribute {}.", attr_name)); + } + + if (pv.size() > 1) { + PUSH_ERROR_AND_RETURN( + fmt::format("Multiple targetPaths assigned to .connection for Attribute {}.", attr_name)); + } + + auto target = pv[0]; + + std::string targetPrimPath = target.prim_part(); + std::string targetPrimPropName = target.prop_part(); + DCOUT("connection targetPath : " << target << "(Prim: " << targetPrimPath + << ", Prop: " << targetPrimPropName + << ")"); + + auto targetPrimRet = + stage.GetPrimAtPath(Path(targetPrimPath, /* prop */ "")); + if (targetPrimRet) { + // Follow the connetion + const Prim *targetPrim = targetPrimRet.value(); + + std::string abs_path = target.full_path_name(); + + TerminalAttributeValue attr_value; + + bool ret = EvaluateAttribute(stage, *targetPrim, targetPrimPropName, + &attr_value, err, t, tinterp); + + if (!ret) { + return false; + } + + if (const auto pav = attr_value.as()) { + (*value) = (*pav); + return true; + } else { + PUSH_ERROR_AND_RETURN( + fmt::format("Attribute of Connection targetPath has different type `{}. Expected `{}`. Attribute `{}`.", attr_value.type_name(), value::TypeTraits::type_name(), attr_name)); + } + + + } else { + PUSH_ERROR_AND_RETURN(targetPrimRet.error()); + } + } else if (attr.is_blocked()) { + PUSH_ERROR_AND_RETURN( + fmt::format("Attribute `{}` is ValueBlocked(None).", attr_name)); + } else { + + return attr.get_value(value); + + } + + return false; +} + + +namespace { + +// Convert TypedAttribute Connection to Attribute Connection. +// If TypedAttribute has value, return Attribute with empty value. +// TODO: make error when Attribute is not 'connection'. +template +Attribute ToAttributeConnection( + const TypedAttribute &input) +{ + Attribute attr; + if (input.is_blocked()) { + attr.set_blocked(true); + attr.variability() = Variability::Uniform; + } else if (input.is_value_empty()) { + // empty = set type info only + attr.set_type_name(input.type_name()); + attr.variability() = Variability::Uniform; + + } else if (input.is_connection()) { + + attr.set_connections(input.connections()); + + } else{ + attr.set_type_name(input.type_name()); + attr.variability() = Variability::Uniform; + } + + return attr; +} + +} // namespace + +template +bool EvaluateTypedAttribute( + const tinyusdz::Stage &stage, const TypedAttribute &tattr, + const std::string &attr_name, + T *value_out, + std::string *err) { + + if (!value_out) { + PUSH_ERROR_AND_RETURN("`value_out` param is nullptr."); + } + + if (tattr.is_blocked()) { + if (err) { + (*err) += "Attribute is Blocked.\n"; + } + return false; + } else if (tattr.is_value_empty()) { + if (err) { + (*err) += "Attribute value is empty.\n"; + } + return false; + } else if (tattr.is_connection()) { + + // Follow targetPath + Attribute attr = ToAttributeConnection(tattr); + + //std::set visited_paths; + + TerminalAttributeValue value; + bool ret = EvaluateAttribute(stage, attr, attr_name, &value, err, + value::TimeCode::Default(), value::TimeSampleInterpolationType::Held); + + if (!ret) { + return false; + } + + if (auto pv = value.as()) { + (*value_out) = *pv; + return true; + } + + if (err) { + (*err) += fmt::format("Type mismatch. Value producing attribute has type {}, but requested type is {}. Attribute: {}", value.type_name(), tattr.type_name(), attr_name); + } + + } else { + if (tattr.get_value(value_out)) { + return true; + } + + if (err) { + (*err) += fmt::format("[Internal error] Invalid TypedAttribute? : {} \n", attr_name); + } + } + return false; +} + +template +bool EvaluateTypedAttribute( + const tinyusdz::Stage &stage, const TypedAttribute &tattr, + const std::string &attr_name, + std::string *value_out, + std::string *err) { + + if (!value_out) { + PUSH_ERROR_AND_RETURN("`value_out` param is nullptr."); + } + + if (tattr.is_blocked()) { + if (err) { + (*err) += "Attribute is Blocked.\n"; + } + return false; + } else if (tattr.is_value_empty()) { + if (err) { + (*err) += "Attribute value is empty.\n"; + } + return false; + } else if (tattr.is_connection()) { + + // Follow targetPath + Attribute attr = ToAttributeConnection(tattr); + + //std::set visited_paths; + + TerminalAttributeValue value; + bool ret = EvaluateAttribute(stage, attr, attr_name, &value, err, + value::TimeCode::Default(), value::TimeSampleInterpolationType::Held); + + if (!ret) { + return false; + } + + if (auto pv = value.as()) { + (*value_out) = *pv; + return true; + } + + // Allow `token` typed value in the attribute of targetPath. + if (auto pv = value.as()) { + // TODO: report an warninig. + (*value_out) = pv->str(); + return true; + } + + if (err) { + (*err) += fmt::format("Type mismatch. Value producing attribute has type {}, but requested type is {}. Attribute: {}", value.type_name(), tattr.type_name(), attr_name); + } + + } else { + if (tattr.get_value(value_out)) { + return true; + } + + if (err) { + (*err) += fmt::format("[Internal error] Invalid TypedAttribute? : {} \n", attr_name); + } + } + return false; +} + +// template instanciations +#define EVALUATE_TYPED_ATTRIBUTE_INSTANCIATE(__ty) \ +template bool EvaluateTypedAttribute(const tinyusdz::Stage &stage, const TypedAttribute<__ty> &attr, const std::string &attr_name, __ty *value, std::string *err); + +APPLY_FUNC_TO_VALUE_TYPES_NO_STRING(EVALUATE_TYPED_ATTRIBUTE_INSTANCIATE) + +#undef EVALUATE_TYPED_ATTRIBUTE_INSTANCIATE + + + + +} // namespace tydra +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval.cc b/contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval.cc new file mode 100644 index 000000000..50191942a --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval.cc @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2024-Present Light Transport Entertainment, Inc. +// +#include "attribute-eval.hh" +#include "scene-access.hh" + +#include "common-macros.inc" +#include "pprinter.hh" +#include "tiny-format.hh" +#include "value-pprint.hh" + +namespace tinyusdz { +namespace tydra { + +// For PUSH_ERROR_AND_RETURN +#define PushError(msg) \ + if (err) { \ + (*err) += msg; \ + } + +namespace { + +bool ToTerminalAttributeValue( + const Attribute &attr, TerminalAttributeValue *value, std::string *err, + const double t, const value::TimeSampleInterpolationType tinterp) { + if (!value) { + // ??? + return false; + } + + if (attr.is_blocked()) { + PUSH_ERROR_AND_RETURN("Attribute is None(Value Blocked)."); + } + + const primvar::PrimVar &var = attr.get_var(); + + value->meta() = attr.metas(); + value->variability() = attr.variability(); + + if (!var.is_valid()) { + PUSH_ERROR_AND_RETURN("[InternalError] Attribute is invalid."); + } else if (var.is_scalar()) { + const value::Value &v = var.value_raw(); + DCOUT("Attribute is scalar type:" << v.type_name()); + DCOUT("Attribute value = " << pprint_value(v)); + + value->set_value(v); + } else if (var.is_timesamples()) { + value::Value v; + if (!var.get_interpolated_value(t, tinterp, &v)) { + PUSH_ERROR_AND_RETURN("Interpolate TimeSamples failed."); + return false; + } + + value->set_value(v); + } + + return true; +} + +// +// visited_paths : To prevent circular referencing of attribute connection. +// +bool EvaluateAttributeImpl( + const tinyusdz::Stage &stage, const tinyusdz::Prim &prim, + const std::string &attr_name, TerminalAttributeValue *value, + std::string *err, std::set &visited_paths, const double t, + const tinyusdz::value::TimeSampleInterpolationType tinterp) { + + DCOUT("Prim : " << prim.element_path().element_name() << "(" + << prim.type_name() << ") attr_name " << attr_name); + + Property prop; + if (!GetProperty(prim, attr_name, &prop, err)) { + DCOUT("Get property failed: " << attr_name); + return false; + } + + if (prop.is_connection()) { + // Follow connection target Path(singple targetPath only). + std::vector pv = prop.get_attribute().connections(); + if (pv.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Connection targetPath is empty for Attribute {}.", attr_name)); + } + + if (pv.size() > 1) { + PUSH_ERROR_AND_RETURN( + fmt::format("Multiple targetPaths assigned to .connection.")); + } + + auto target = pv[0]; + + std::string targetPrimPath = target.prim_part(); + std::string targetPrimPropName = target.prop_part(); + DCOUT("connection targetPath : " << target << "(Prim: " << targetPrimPath + << ", Prop: " << targetPrimPropName + << ")"); + + auto targetPrimRet = + stage.GetPrimAtPath(Path(targetPrimPath, /* prop */ "")); + if (targetPrimRet) { + // Follow the connetion + const Prim *targetPrim = targetPrimRet.value(); + + std::string abs_path = target.full_path_name(); + + if (visited_paths.count(abs_path)) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Circular referencing detected. connectionTargetPath = {}", + to_string(target))); + } + visited_paths.insert(abs_path); + + return EvaluateAttributeImpl(stage, *targetPrim, targetPrimPropName, + value, err, visited_paths, t, tinterp); + + } else { + PUSH_ERROR_AND_RETURN(targetPrimRet.error()); + } + } else if (prop.is_relationship()) { + PUSH_ERROR_AND_RETURN( + fmt::format("Property `{}` is a Relation.", attr_name)); + } else if (prop.is_attribute()) { + DCOUT("IsAttrib"); + + const Attribute &attr = prop.get_attribute(); + + if (attr.is_blocked()) { + PUSH_ERROR_AND_RETURN( + fmt::format("Attribute `{}` is ValueBlocked(None).", attr_name)); + } + + if (!ToTerminalAttributeValue(attr, value, err, t, tinterp)) { + return false; + } + + } else if (prop.is_empty()) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Attribute `{}` is a define-only attribute(no value assigned).", + attr_name)); + } else { + // ??? + PUSH_ERROR_AND_RETURN( + fmt::format("[InternalError] Invalid Attribute `{}`.", attr_name)); + } + + return true; +} + +bool EvaluateAttributeImpl( + const tinyusdz::Stage &stage, const tinyusdz::Attribute &attr, + const std::string &attr_name, TerminalAttributeValue *value, + std::string *err, std::set &visited_paths, const double t, + const tinyusdz::value::TimeSampleInterpolationType tinterp) { + + if (attr.is_connection()) { + // Follow connection target Path(singple targetPath only). + std::vector pv = attr.connections(); + if (pv.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Connection targetPath is empty for Attribute {}.", attr_name)); + } + + if (pv.size() > 1) { + PUSH_ERROR_AND_RETURN( + fmt::format("Multiple targetPaths assigned to .connection.")); + } + + auto target = pv[0]; + + std::string targetPrimPath = target.prim_part(); + std::string targetPrimPropName = target.prop_part(); + DCOUT("connection targetPath : " << target << "(Prim: " << targetPrimPath + << ", Prop: " << targetPrimPropName + << ")"); + + auto targetPrimRet = + stage.GetPrimAtPath(Path(targetPrimPath, /* prop */ "")); + if (targetPrimRet) { + // Follow the connetion + const Prim *targetPrim = targetPrimRet.value(); + + std::string abs_path = target.full_path_name(); + + if (visited_paths.count(abs_path)) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Circular referencing detected. connectionTargetPath = {}", + to_string(target))); + } + visited_paths.insert(abs_path); + + return EvaluateAttributeImpl(stage, *targetPrim, targetPrimPropName, + value, err, visited_paths, t, tinterp); + + } else { + PUSH_ERROR_AND_RETURN(targetPrimRet.error()); + } + } else if (attr.is_blocked()) { + PUSH_ERROR_AND_RETURN( + fmt::format("Attribute `{}` is ValueBlocked(None).", attr_name)); + } else { + + if (!ToTerminalAttributeValue(attr, value, err, t, tinterp)) { + return false; + } + + } + + return true; +} + +} // namespace + +bool EvaluateAttribute( + const tinyusdz::Stage &stage, const tinyusdz::Prim &prim, + const std::string &attr_name, TerminalAttributeValue *value, + std::string *err, const double t, + const tinyusdz::value::TimeSampleInterpolationType tinterp) { + std::set visited_paths; + + return EvaluateAttributeImpl(stage, prim, attr_name, value, err, + visited_paths, t, tinterp); +} + +bool EvaluateAttribute( + const tinyusdz::Stage &stage, const Attribute &attr, + const std::string &attr_name, TerminalAttributeValue *value, + std::string *err, const double t, + const tinyusdz::value::TimeSampleInterpolationType tinterp) { + std::set visited_paths; + + return EvaluateAttributeImpl(stage, attr, attr_name, value, err, + visited_paths, t, tinterp); +} + +} // namespace tydra +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval.hh b/contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval.hh new file mode 100644 index 000000000..a7643e393 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/attribute-eval.hh @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2024-Present Light Transport Entertainment, Inc. +// +// Evaluate Attribute API +// +// TODO: +// - [ ] Reduce template code to speed-up compilation +// +#pragma once + +#include + +#include "prim-types.hh" +#include "stage.hh" +#include "usdGeom.hh" +#include "usdShade.hh" +#include "usdSkel.hh" +#include "usdLux.hh" +#include "value-types.hh" +#include "value-type-macros.inc" +#include "prim-type-macros.inc" +#include "tiny-format.hh" + +namespace tinyusdz { +namespace tydra { + +/// +/// Terminal Attribute value at specified timecode. +/// +/// - No `None`(Value Blocked) +/// - No connection(connection target is followed and resolved(fetch 'value producing attribute' in pxrUSD terminology)) +/// - No timeSampled value +/// +class TerminalAttributeValue { + public: + TerminalAttributeValue() = default; + + TerminalAttributeValue(const value::Value &v) : _empty{false}, _value(v) {} + TerminalAttributeValue(value::Value &&v) + : _empty{false}, _value(std::move(v)) {} + + // "empty" attribute(type info only) + void set_empty_attribute(const std::string &type_name) { + _empty = true; + _type_name = type_name; + } + + TerminalAttributeValue(const std::string &type_name) { + set_empty_attribute(type_name); + } + + bool is_empty() const { return _empty; } + + template + const T *as() const { + if (_empty) { + return nullptr; + } + return _value.as(); + } + + template + bool is() const { + if (_empty) { + return false; + } + + if (_value.as()) { + return true; + } + return false; + } + + void set_value(const value::Value &v) { + _value = v; + _empty = false; + } + + void set_value(value::Value &&v) { + _value = std::move(v); + _empty = false; + } + + const std::string type_name() const { + if (_empty) { + return _type_name; + } + + return _value.type_name(); + } + + uint32_t type_id() const { + if (_empty) { + return value::GetTypeId(_type_name); + } + + return _value.type_id(); + } + + Variability variability() const { return _variability; } + Variability &variability() { return _variability; } + + const AttrMeta &meta() const { return _meta; } + AttrMeta &meta() { return _meta; } + + private: + bool _empty{true}; + std::string _type_name; + Variability _variability{Variability::Varying}; + value::Value _value{nullptr}; + AttrMeta _meta; +}; + +/// +/// Evaluate Attribute of the specied Prim and retrieve terminal Attribute +/// value. +/// +/// - If the attribute is empty(e.g. `float outputs:r`), return "empty" +/// Attribute +/// - If the attribute is scalar value, simply returns it. +/// - If the attribute is timeSamples value, evaluate the value at specified +/// time. +/// - If the attribute is connection, follow the connection target +/// +/// @param[in] stage Stage +/// @param[in] prim Prim +/// @param[in] attr_name Attribute name +/// @param[out] value Evaluated terminal attribute value. +/// @param[out] err Error message(filled when false returned). Set nullptr if you don't need error message. +/// @param[in] t (optional) TimeCode(for timeSamples Attribute) +/// @param[in] tinterp (optional) Interpolation type for timeSamples value +/// +/// Return false when: +/// +/// - If the attribute is None(ValueBlock) +/// - Requested attribute not found in a Prim. +/// - Invalid connection(e.g. type mismatch, circular referencing, targetPath +/// points non-existing path, etc), +/// - Other error happens. +/// +bool EvaluateAttribute( + const tinyusdz::Stage &stage, const tinyusdz::Prim &prim, + const std::string &attr_name, TerminalAttributeValue *value, + std::string *err, const double t = tinyusdz::value::TimeCode::Default(), + const tinyusdz::value::TimeSampleInterpolationType tinterp = + tinyusdz::value::TimeSampleInterpolationType::Linear); + +/// +/// Evaluate Attribute and retrieve terminal Attribute value. +/// +/// - If the attribute is empty(e.g. `float outputs:r`), return "empty" +/// Attribute +/// - If the attribute is scalar value, simply returns it. +/// - If the attribute is timeSamples value, evaluate the value at specified +/// time. +/// - If the attribute is connection, follow the connection target +/// +/// @param[in] stage Stage +/// @param[in] attr Attribute +/// @param[in] attr_name Attribute name. This is only used in error message, so it can be empty. +/// @param[out] value Evaluated terminal attribute value. +/// @param[out] err Error message(filled when false returned). Set nullptr if you don't need error message. +/// @param[in] t (optional) TimeCode(for timeSamples Attribute) +/// @param[in] tinterp (optional) Interpolation type for timeSamples value +/// +bool EvaluateAttribute( + const tinyusdz::Stage &stage, const Attribute &attr, + const std::string &attr_name, TerminalAttributeValue *value, + std::string *err, const double t = tinyusdz::value::TimeCode::Default(), + const tinyusdz::value::TimeSampleInterpolationType tinterp = + tinyusdz::value::TimeSampleInterpolationType::Linear); + + +// +// Typed version +// +template +bool EvaluateTypedAttribute( + const tinyusdz::Stage &stage, + const TypedAttribute &attr, + const std::string &attr_name, + T *value, + std::string *err); + + +// NOTE: std::string uses the specialization, so no extern template. +#define EXTERN_EVALUATE_TYPED_ATTRIBUTE(__ty) \ +extern template bool EvaluateTypedAttribute(const tinyusdz::Stage &stage, const TypedAttribute<__ty> &attr, const std::string &attr_name, __ty *value, std::string *err); + +APPLY_FUNC_TO_VALUE_TYPES_NO_STRING(EXTERN_EVALUATE_TYPED_ATTRIBUTE) +template<> bool EvaluateTypedAttribute(const tinyusdz::Stage &stage, const TypedAttribute &attr, const std::string &attr_name, std::string *value, std::string *err); + +#undef EXTERN_EVALUATE_TYPED_ATTRIBUTE + +template +bool EvaluateTypedAnimatableAttribute( + const tinyusdz::Stage &stage, + const TypedAttribute> &attr, + const std::string &attr_name, + T *value, + std::string *err, const double t = tinyusdz::value::TimeCode::Default(), + const tinyusdz::value::TimeSampleInterpolationType tinterp = + tinyusdz::value::TimeSampleInterpolationType::Linear); + +#define EXTERN_EVALUATE_TYPED_ATTRIBUTE(__ty) \ +extern template bool EvaluateTypedAnimatableAttribute(const tinyusdz::Stage &stage, const TypedAttribute> &attr, const std::string &attr_name, __ty *value, std::string *err, const double t, const value::TimeSampleInterpolationType tinter); + +APPLY_FUNC_TO_VALUE_TYPES_NO_STRING(EXTERN_EVALUATE_TYPED_ATTRIBUTE) +template<> bool EvaluateTypedAnimatableAttribute(const tinyusdz::Stage &stage, const TypedAttribute> &attr, const std::string &attr_name, std::string *value, std::string *err, const double t, const value::TimeSampleInterpolationType tinter); + +#undef EXTERN_EVALUATE_TYPED_ATTRIBUTE + +template +bool EvaluateTypedAttribute( + const tinyusdz::Stage &stage, + const TypedAttributeWithFallback &attr, + const std::string &attr_name, + T *value, + std::string *err); + +#define EXTERN_EVALUATE_TYPED_ATTRIBUTE(__ty) \ +extern template bool EvaluateTypedAttribute(const tinyusdz::Stage &stage, const TypedAttributeWithFallback<__ty> &attr, const std::string &attr_name, __ty *value, std::string *err); + +APPLY_FUNC_TO_VALUE_TYPES_NO_STRING(EXTERN_EVALUATE_TYPED_ATTRIBUTE) +template<> bool EvaluateTypedAttribute( + const tinyusdz::Stage &stage, + const TypedAttributeWithFallback &attr, + const std::string &attr_name, + std::string *value, + std::string *err); + +#undef EXTERN_EVALUATE_TYPED_ATTRIBUTE + +template +bool EvaluateTypedAnimatableAttribute( + const tinyusdz::Stage &stage, + const TypedAttributeWithFallback> &attr, + const std::string &attr_name, + T *value, + std::string *err, const double t = tinyusdz::value::TimeCode::Default(), + const tinyusdz::value::TimeSampleInterpolationType tinterp = + tinyusdz::value::TimeSampleInterpolationType::Linear); + +#define EXTERN_EVALUATE_TYPED_ATTRIBUTE(__ty) \ +extern template bool EvaluateTypedAnimatableAttribute(const tinyusdz::Stage &stage, const TypedAttributeWithFallback> &attr, const std::string &attr_name, __ty *value, std::string *err, const double t, const value::TimeSampleInterpolationType tinter); + +APPLY_FUNC_TO_VALUE_TYPES_NO_STRING(EXTERN_EVALUATE_TYPED_ATTRIBUTE) +template<> bool EvaluateTypedAnimatableAttribute( + const tinyusdz::Stage &stage, + const TypedAttributeWithFallback> &attr, + const std::string &attr_name, + std::string *value, + std::string *err, const double t, const tinyusdz::value::TimeSampleInterpolationType tinterp); + +#undef EXTERN_EVALUATE_TYPED_ATTRIBUTE + +} // namespace tydra +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/facial.cc b/contrib/tinyusdz/tinyusdz_repo/src/tydra/facial.cc new file mode 100644 index 000000000..6f6a3d2d3 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/facial.cc @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2023 - Present, Light Transport Entertainment, Inc. + +#include +#include +#include + +// +#include "facial.hh" + +namespace tinyusdz { +namespace tydra { + +constexpr std::array gARKitBlendShapeLocationKV = { + "blowDownLeft", + "browDownRight", + "browInnerUp", + "browOuterUpLeft", + "browOuterUpRight", + "cheekPuff", + "cheekSquintLeft", + "cheekSquintRight", + "eyeBlinkLeft", + "eyeBlinkRight", + "eyeLookDownLeft", + "eyeLookDownRight", + "eyeLookInLeft", + "eyeLookInRight", + "eyeLookOutLeft", + "eyeLookOutRight", + "eyeLookUpLeft", + "eyeLookUpRight", + "eyeSquintLeft", + "eyeSquintRight", + "eyeWideLeft", + "eyeWideRight", + "jawForward", + "jawLeft", + "jawOpen", + "jawRight", + "mouthClose", + "mouthDimpleLeft", + "mouthDimpleRight", + "mouthFrownLeft", + "mouthFrownRight", + "mouthFunnel", + "mouthLeft", + "mouthLowerDownLeft", + "mouthLowerDownRight", + "mouthPressLeft", + "mouthPressRight", + "mouthPucker", + "mouthRight", + "mouthRollLower", + "mouthRollUpper", + "mouthShrugLower", + "mouthShrugUpper", + "mouthSmileLeft", + "mouthSmileRight", + "mouthStretchLeft", + "mouthStretchRight", + "mouthUpperUpLeft", + "mouthUpperUpRight", + "noseSneerLeft", + "noseSneerRight", + "tongueOut"}; + +std::string GetARKitBlendShapeLocationString( + const ARKitBlendShapeLocation loc) { + uint32_t i = static_cast(loc); + if (loc > 51) { + return "[[InvalidBlendShapeLocationName]]"; + } + + return gARKitBlendShapeLocationKV[i]; +} + +bool GetARKitBlendShapeLocationEnumFromString(const std::string &s, + ARKitBlendShapeLocation *loc) { + if (!loc) { + return false; + } + + // simple linear scan + for (size_t i = 0; i < 52; i++) { + if (s.compare(gARKitBlendShapeLocationKV[i]) == 0) { + (*loc) = static_cast(i); + return true; + } + } + + return false; +} + +} // namespace tydra +} // namespace tinyusdz + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/facial.hh b/contrib/tinyusdz/tinyusdz_repo/src/tydra/facial.hh new file mode 100644 index 000000000..3f9778048 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/facial.hh @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2023 - Present, Light Transport Entertainment, Inc. +#pragma once + +#include + +namespace tinyusdz { +namespace tydra { + +// +// ARKit compatible BlendShape target names(52 shape targets). +// + +enum ARKitBlendShapeLocation { + BrowDownLeft = 0, + BrowDownRight, + BrowInnerUp, + BrowOuterUpLeft, + BrowOuterUpRight, + CheekPuff, + CheekSquintLeft, + CheekSquintRight, + EyeBlinkLeft, + EyeBlinkRight, + EyeLookDownLeft, + EyeLookDownRight, + EyeLookInLeft, + EyeLookInRight, + EyeLookOutLeft, + EyeLookOutRight, + EyeLookUpLeft, + EyeLookUpRight, + EyeSquintLeft, + EyeSquintRight, + EyeWideLeft, + EyeWideRight, + JawForward, + JawLeft, + JawOpen, + JawRight, + MouthClose, + MouthDimpleLeft, + MouthDimpleRight, + MouthFrownLeft, + MouthFrownRight, + MouthFunnel, + MouthLeft, + MouthLowerDownLeft, + MouthLowerDownRight, + MouthPressLeft, + MouthPressRight, + MouthPucker, + MouthRight, + MouthRollLower, + MouthRollUpper, + MouthShrugLower, + MouthShrugUpper, + MouthSmileLeft, + MouthSmileRight, + MouthStretchLeft, + MouthStretchRight, + MouthUpperUpLeft, + MouthUpperUpRight, + NoseSneerLeft, + NoseSneerRight, + TongueOut // = 52 +}; + +std::string GetARKitBlendShapeLocationString( + const ARKitBlendShapeLocation loc); +bool GetARKitBlendShapeLocationEnumFromString(const std::string &name, + ARKitBlendShapeLocation *loc); + +} // namespace tydra +} // namespace tinyusdz + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/nurbs-tess.hh b/contrib/tinyusdz/tinyusdz_repo/src/tydra/nurbs-tess.hh new file mode 100644 index 000000000..affd98242 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/nurbs-tess.hh @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache 2.0 +// Simple NURBS tesselation + +#pragma once + +#include "render-data.hh" + +namespace tinyusdz { + +namespace tydra { + +struct Nurbs; + +class NurbsTesselator +{ + bool tesselate(const Nurbs &nurbs, uint32_t u_divs, uint32_t v_divs, RenderMesh &dst ); +}; + +} // namespace tydra + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/obj-export.cc b/contrib/tinyusdz/tinyusdz_repo/src/tydra/obj-export.cc new file mode 100644 index 000000000..a71526231 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/obj-export.cc @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2024 - Present, Light Transport Entertainment Inc. +// +#include +#include +#include + +#include "obj-export.hh" +#include "common-macros.inc" +#include "tiny-format.hh" + +namespace tinyusdz { +namespace tydra { + +#define PushError(msg) { \ + if (err) { \ + (*err) += msg + "\n"; \ + } \ +} + +bool export_to_obj(const RenderScene &scene, const int mesh_id, + std::string &obj_str, std::string &mtl_str, std::string *warn, std::string *err) { + + // + // NOTE: + // + // - Export GeomSubset(per-face material) as group(g) + usemtl + // - Export skin weight as tinyobjloader's 'vw' extension + // + + (void)obj_str; + (void)mtl_str; + (void)warn; + + std::stringstream ss; + + if (mesh_id < 0) { + PUSH_ERROR_AND_RETURN("Invalid mesh_id"); + } else if (size_t(mesh_id) >= scene.meshes.size()) { + PUSH_ERROR_AND_RETURN(fmt::format("mesh_id {} is out-of-range. scene.meshes.size {}", mesh_id, scene.meshes.size())); + } + + const RenderMesh &mesh = scene.meshes[size_t(mesh_id)]; + + ss << "# exported from TinyUSDZ Tydra.\n"; + ss << "mtllib " << mesh_id << mesh.prim_name + ".mtl"; + ss << "\n"; + + for (size_t i = 0; i < mesh.points.size(); i++) { + ss << "v " << mesh.points[i][0] << " " << mesh.points[i][1] << " " << mesh.points[i][2] << "\n"; + } + ss << "# " << mesh.points.size() << " vertices\n"; + + if (mesh.joint_and_weights.jointWeights.size() == (mesh.points.size() * size_t(mesh.joint_and_weights.elementSize))) { + + size_t elementSize = size_t(mesh.joint_and_weights.elementSize); // # of weights per vertex. + for (size_t i = 0; i < mesh.points.size(); i++) { + ss << "vw "; + for (size_t w = 0; w < elementSize; w++) { + if (w > 0) { + ss << " "; + } + ss << mesh.joint_and_weights.jointIndices[i * elementSize + w] << " " << mesh.joint_and_weights.jointWeights[i * elementSize + w]; + } + ss << "\n"; + } + } + + bool has_texcoord = false; + bool is_facevarying_texcoord = false; + bool has_normal = false; + bool is_facevarying_normal = false; + + // primary texcoord only + if (mesh.texcoords.count(0)) { + const VertexAttribute &texcoord = mesh.texcoords.at(0); + if (texcoord.variability == VertexVariability::FaceVarying) { + is_facevarying_texcoord = true; + } else if (texcoord.variability == VertexVariability::Vertex) { + is_facevarying_texcoord = false; + } else { + PUSH_ERROR_AND_RETURN("Vertex variability must be either 'vertex' or 'facevarying' for texcoord0"); + } + if (texcoord.format == VertexAttributeFormat::Vec2) { + const float *ptr = reinterpret_cast(texcoord.buffer()); + for (size_t i = 0; i < texcoord.vertex_count(); i++) { + ss << "vt " << ptr[2 * i + 0] << " " << ptr[2 * i + 1] << "\n"; + } + + has_texcoord = true; + } + } + + if (!mesh.normals.empty()) { + if (mesh.normals.variability == VertexVariability::FaceVarying) { + is_facevarying_normal = true; + } else if (mesh.normals.variability == VertexVariability::Vertex) { + is_facevarying_normal = false; + } else { + PUSH_ERROR_AND_RETURN("Vertex variability must be either 'vertex' or 'facevarying' for texcoord0"); + } + if (mesh.normals.format == VertexAttributeFormat::Vec3) { + const float *ptr = reinterpret_cast(mesh.normals.buffer()); + for (size_t i = 0; i < mesh.normals.vertex_count(); i++) { + ss << "vn " << ptr[3 * i + 0] << " " << ptr[3 * i + 1] << " " << ptr[3 * i + 2] << "\n"; + } + has_normal = true; + } + } + + // name -> (mat_id, face_ids) + std::unordered_map>> face_groups; + if (mesh.material_subsetMap.size()) { + + std::unordered_set subset_face_ids; + + for (const auto &subset : mesh.material_subsetMap) { + std::vector face_ids(subset.second.indices().size()); + for (size_t i = 0; i < subset.second.indices().size(); i++) { + face_ids[i] = uint32_t(subset.second.indices()[i]); + subset_face_ids.insert(face_ids[i]); + } + if (subset.first.empty()) { + PUSH_ERROR_AND_RETURN("Empty material_subset name is not allowed."); + } + face_groups[subset.first] = std::make_pair(subset.second.material_id, face_ids); + } + + // face_ids without materialsubset + std::vector face_ids; + for (size_t i = 0; i < mesh.faceVertexCounts().size(); i++) { + if (!subset_face_ids.count(uint32_t(i))) { + face_ids.push_back(uint32_t(i)); + } + } + face_groups[""] = std::make_pair(mesh.material_id, face_ids); + } else { + std::vector face_ids(mesh.faceVertexCounts().size()); + std::iota(face_ids.begin(), face_ids.end(), 0); + + face_groups[""] = std::make_pair(mesh.material_id, face_ids); + } + + // build face_id -> location in mesh.faceVertexIndices table. + std::vector offsets(mesh.faceVertexCounts().size()); + size_t offset = 0; + for (size_t i = 0; i < mesh.faceVertexCounts().size(); i++) { + offsets[i] = offset; + offset += mesh.faceVertexCounts()[i]; + } + + size_t faceIndexOffset = 0; + // Assume empty group name is iterated first. + for (const auto &group : face_groups) { + + if (group.first.size()) { + ss << "g " << group.first << "\n"; + } + + if (std::get<0>(group.second) > -1) { + uint32_t mat_id = uint32_t(std::get<0>(group.second)); + ss << "usemtl " << scene.materials[mat_id].name << "\n"; + } + + const auto &face_ids = std::get<1>(group.second); + + for (size_t i = 0; i < face_ids.size(); i++) { + ss << "f "; + + for (size_t f = 0; f < mesh.faceVertexCounts()[face_ids[i]]; f++) { + if (f > 0) { + ss << " "; + } + // obj's index starts with 1. + uint32_t idx = mesh.faceVertexIndices()[offsets[face_ids[i]] + f] + 1; + + uint32_t t_idx = is_facevarying_texcoord ? uint32_t(faceIndexOffset + f) : idx; + uint32_t n_idx = is_facevarying_normal ? uint32_t(faceIndexOffset + f) : idx; + + if (has_texcoord && has_normal) { + ss << idx << "/" << t_idx << "/" << n_idx; + } else if (has_texcoord) { + ss << idx << "/" << t_idx; + } else if (has_normal) { + ss << idx << "//" << n_idx; + } else { + ss << idx; + } + } + + faceIndexOffset += mesh.faceVertexIndices()[face_ids[i]]; + ss << "\n"; + } + + ss << "\n"; + } + + obj_str = ss.str(); + + ss.str(""); + ss << "# exported from TinyUSDZ Tydra.\n"; + + // emit material info + for (const auto &group : face_groups) { + if (group.second.first == -1) { + continue; + } + + uint32_t mat_id = uint32_t(std::get<0>(group.second)); + ss << "newmtl " << scene.materials[mat_id].name << "\n"; + + // Diffuse only + // TODO: Emit more PBR material + if (scene.materials[mat_id].surfaceShader.diffuseColor.is_texture()) { + int32_t texId = scene.materials[mat_id].surfaceShader.diffuseColor.textureId; + if ((texId < 0) || (texId >= int(scene.textures.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid texture id {}. scene.textures.size = {}", texId, scene.textures.size())); + } + + int64_t imageId = scene.textures[size_t(texId)].texture_image_id; + if ((imageId < 0) || (imageId >= int64_t(scene.images.size()))) { + PUSH_ERROR_AND_RETURN(fmt::format("Invalid image id {}. scene.images.size = {}", imageId, scene.images.size())); + } + + std::string texname = scene.images[size_t(imageId)].asset_identifier; + if (texname.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Filename for image id {} is empty.", imageId)); + } + ss << "map_Kd " << texname << "\n"; + } else { + const auto col = scene.materials[mat_id].surfaceShader.diffuseColor.value; + ss << "Kd " << col[0] << " " << col[1] << " " << col[2] << "\n"; + } + + ss << "\n"; + } + ss << "# " << face_groups.size() << " materials.\n"; + + mtl_str = ss.str(); + + return true; +} + + +} // namespace tydra +} // namespace tinyusdz + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/obj-export.hh b/contrib/tinyusdz/tinyusdz_repo/src/tydra/obj-export.hh new file mode 100644 index 000000000..c5e87db19 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/obj-export.hh @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2024 - Present, Light Transport Entertainment Inc. +// +// Simple RenderMesh/RenderMaterial -> wavefront .obj exporter +// +#pragma once + +#include "render-data.hh" + +namespace tinyusdz { +namespace tydra { + +/// +/// Export RenderMesh/RenderMaterial to .obj. +/// Requires RenderScene instance to export Material/Texture correctly. +/// +/// NOTE: No consideration of up-Axis. 3D coordinate is exported as-as. +/// Thus, if your USD scene is Z-up, 3D coordinate in exported .obj is Z-up. +/// +/// (fortunately, you can import .obj by specifying Z-up axis in Blender) +/// +/// @param[in] scene RenderScene +/// @param[in] mesh_id Mesh id in RenderScene +/// @param[out] obj_str .obj string +/// @param[out] warn warning message +/// @param[out] err error message +/// +/// @return true upon success. +/// +bool export_to_obj(const RenderScene &scene, const int mesh_id, + std::string &obj_str, std::string &mtl_str, + std::string *warn, std::string *err); + +} // namespace tydra +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/prim-apply.cc b/contrib/tinyusdz/tinyusdz_repo/src/tydra/prim-apply.cc new file mode 100644 index 000000000..e08560b3b --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/prim-apply.cc @@ -0,0 +1,157 @@ +#include "prim-apply.hh" + +#include "prim-types.hh" +#include "usdGeom.hh" +#include "usdSkel.hh" +#include "usdLux.hh" + +namespace tinyusdz { +namespace tydra { + +bool ApplyToGPrim( + const Stage &stage, const Prim &prim, + std::function fn) { + + (void)stage; + + if ((value::TypeId::TYPE_ID_GPRIM <= prim.type_id() && + (value::TypeId::TYPE_ID_GEOM_END > prim.type_id()))) { + // gprim + } else { + return false; + } + +#define APPLY_FUN(__ty) { \ + const auto *v = prim.as<__ty>(); \ + if (v) { \ + return fn(stage, v); \ + } \ + } + + APPLY_FUN(GPrim) + APPLY_FUN(Xform) + APPLY_FUN(GeomMesh) + APPLY_FUN(GeomSphere) + APPLY_FUN(GeomCapsule) + APPLY_FUN(GeomCube) + APPLY_FUN(GeomPoints) + APPLY_FUN(GeomCylinder) + APPLY_FUN(GeomBasisCurves) + +#undef APPLY_FUN + + return false; + +} + +bool ApplyToMaterialBinding( + const Stage &stage, const Prim &prim, + std::function fn) { + + if ((value::TypeId::TYPE_ID_GPRIM <= prim.type_id() && + (value::TypeId::TYPE_ID_GEOM_END > prim.type_id()))) { + // gprim or geomsubset + } else { + return false; + } + +#define APPLY_FUN(__ty) { \ + const auto *v = prim.as<__ty>(); \ + if (v) { \ + return fn(stage, v); \ + } \ + } + + // TODO: Model/Scope + APPLY_FUN(Model) + APPLY_FUN(Scope) + APPLY_FUN(GPrim) + APPLY_FUN(Xform) + APPLY_FUN(GeomMesh) + APPLY_FUN(GeomSphere) + APPLY_FUN(GeomCapsule) + APPLY_FUN(GeomCube) + APPLY_FUN(GeomPoints) + APPLY_FUN(GeomCylinder) + APPLY_FUN(GeomBasisCurves) + APPLY_FUN(GeomSubset) + +#undef APPLY_FUN + + return false; + +} + +bool ApplyToCollection( + const Prim &prim, + std::function fn) { + + if ((value::TypeId::TYPE_ID_GPRIM <= prim.type_id() && + (value::TypeId::TYPE_ID_GEOM_END > prim.type_id()))) { + // gprim or geomsubset + } else if ((value::TypeId::TYPE_ID_LUX_BEGIN <= prim.type_id() && + (value::TypeId::TYPE_ID_LUX_END > prim.type_id()))) { + // usdLux + } else { + return false; + } + +#define APPLY_FUN(__ty) { \ + const auto *v = prim.as<__ty>(); \ + if (v) { \ + return fn(v); \ + } \ + } + + APPLY_FUN(Model) + APPLY_FUN(Scope) + APPLY_FUN(GPrim) + APPLY_FUN(Xform) + APPLY_FUN(GeomMesh) + APPLY_FUN(GeomSphere) + APPLY_FUN(GeomCapsule) + APPLY_FUN(GeomCube) + APPLY_FUN(GeomPoints) + APPLY_FUN(GeomCylinder) + APPLY_FUN(GeomBasisCurves) + APPLY_FUN(GeomSubset) + APPLY_FUN(SphereLight) + +#undef APPLY_FUN + + return false; + +} + +bool ApplyToXformable( + const Stage &stage, const Prim &prim, + std::function fn) { + + (void)stage; + +#define APPLY_FUN(__ty) { \ + const auto *v = prim.as<__ty>(); \ + if (v) { \ + return fn(stage, v); \ + } \ + } + + APPLY_FUN(GPrim) + APPLY_FUN(Xform) + APPLY_FUN(GeomMesh) + APPLY_FUN(GeomSphere) + APPLY_FUN(GeomCapsule) + APPLY_FUN(GeomCube) + APPLY_FUN(GeomPoints) + APPLY_FUN(GeomCylinder) + APPLY_FUN(GeomBasisCurves) + APPLY_FUN(SkelRoot) + +#undef APPLY_FUN + + return false; + +} + +} // namespace tydra +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/prim-apply.hh b/contrib/tinyusdz/tinyusdz_repo/src/tydra/prim-apply.hh new file mode 100644 index 000000000..42603a1c0 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/prim-apply.hh @@ -0,0 +1,45 @@ +#pragma once + +// utility to apply function to a Prim. +// Internal use expected(not intended for Public Tydra API). + +#include + +namespace tinyusdz { + +// forward decl. +class Stage; +class Prim; +class MaterialBinding; +struct GPrim; +struct Xformable; + +class Collection; // Collection API + +namespace tydra { + +bool ApplyToGPrim( + const Stage &stage, const Prim &prim, + std::function fn); + +// +// Prim which inherits MaterialBinding, i.e, GPrim and GeomSubset. +// +bool ApplyToMaterialBinding( + const Stage &stage, const Prim &prim, + std::function fn); + +bool ApplyToXformable( + const Stage &stage, const Prim &prim, + std::function fn); + +bool ApplyToGPrim( + const Prim &prim, + std::function fn); + +bool ApplyToCollection( + const Prim &prim, + std::function fn); + +} // namespace tydra +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/render-data.cc b/contrib/tinyusdz/tinyusdz_repo/src/tydra/render-data.cc new file mode 100644 index 000000000..a2fb129a6 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/render-data.cc @@ -0,0 +1,6541 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +// TODO: +// - [ ] Subdivision surface to polygon mesh conversion. +// - [ ] Correctly handle primvar with 'vertex' interpolation(Use the basis +// function of subd surface) +// - [x] Support time-varying shader attribute(timeSamples) +// - [ ] Wide gamut colorspace conversion support +// - [ ] linear sRGB <-> linear DisplayP3 +// - [x] Compute tangentes and binormals +// - [x] displayColor, displayOpacity primvar(vertex color) +// - [ ] Support Inbetween BlendShape +// - [ ] Support material binding collection(Collection API) +// - [ ] Support multiple skel animation +// https://github.com/PixarAnimationStudios/OpenUSD/issues/2246 +// - [ ] Adjust normal vector computation with handness? +// - [ ] Node xform animation +// - [ ] Better build of index buffer +// - [ ] Preserve the order of 'points' variable(mesh.points, Skin +// indices/weights, BlendShape points, ...) as much as possible. +// - Implement spatial hash +// +#include + +#include "image-loader.hh" +#include "image-util.hh" +#include "image-types.hh" +#include "linear-algebra.hh" +#include "math-util.inc" +#include "pprinter.hh" +#include "prim-types.hh" +#include "str-util.hh" +#include "tiny-format.hh" +#include "tinyusdz.hh" +#include "usdGeom.hh" +#include "usdShade.hh" +#include "value-pprint.hh" + +#if defined(TINYUSDZ_WITH_COLORIO) +#include "external/tiny-color-io.h" +#endif + +#include "../../../assimp_tinyusdz_logging.inc" + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +// For tangent/binormal computation +// NOTE: HalfEdge is not used atm. +#include "external/half-edge.hh" + +// For triangulation. +// TODO: Use tinyobjloader's triangulation +#include "external/mapbox/earcut/earcut.hpp" + +// For kNN point search +// #include "external/nanoflann.hpp" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// +#include "common-macros.inc" +#include "math-util.inc" + +// +#include "tydra/attribute-eval.hh" +#include "tydra/render-data.hh" +#include "tydra/scene-access.hh" +#include "tydra/shader-network.hh" + +namespace tinyusdz { + +namespace tydra { + +namespace { + +#define PushError(msg) \ + if (err) { \ + (*err) += msg; \ + } + +inline std::string to_string(const UVTexture::Channel channel) { + if (channel == UVTexture::Channel::RGB) { + return "rgb"; + } else if (channel == UVTexture::Channel::R) { + return "r"; + } else if (channel == UVTexture::Channel::G) { + return "g"; + } else if (channel == UVTexture::Channel::B) { + return "b"; + } else if (channel == UVTexture::Channel::A) { + return "a"; + } + + return "[[InternalError. Invalid UVTexture::Channel]]"; +} + +// +// Convert vertex attribute with Uniform variability(interpolation) to +// facevarying variability, by replicating uniform value per face over face +// vertices. +// +#if 0 // unused atm +template +nonstd::expected, std::string> UniformToFaceVarying( + const std::vector &inputs, + const std::vector &faceVertexCounts) { + std::vector dst; + + if (inputs.size() == faceVertexCounts.size()) { + return nonstd::make_unexpected( + fmt::format("The number of inputs {} must be the same with " + "faceVertexCounts.size() {}", + inputs.size(), faceVertexCounts.size())); + } + + for (size_t i = 0; i < faceVertexCounts.size(); i++) { + size_t cnt = faceVertexCounts[i]; + + // repeat cnt times. + for (size_t k = 0; k < cnt; k++) { + dst.emplace_back(inputs[i]); + } + } + + return dst; +} +#endif + +// +// Convert vertex attribute with Uniform variability(interpolation) to vertex +// variability, by replicating uniform value for vertices of a face. For shared +// vertex, the value will be overwritten. +// +#if 0 // unused atm +template +nonstd::expected, std::string> UniformToVertex( + const std::vector &inputs, const size_t elementSize, + const std::vector &faceVertexCounts, + const std::vector &faceVertexIndices) { + std::vector dst; + + if (faceVertexIndices.size() < 3) { + return nonstd::make_unexpected( + fmt::format("faceVertexIndices.size must be 3 or greater, but got {}.", + faceVertexCounts.size())); + } + + if (faceVertexCounts.empty()) { + return nonstd::make_unexpected("faceVertexCounts.size is zero"); + } + + if (elementSize == 0) { + return nonstd::make_unexpected("`elementSize` is zero."); + } + + if ((inputs.size() % elementSize) != 0) { + return nonstd::make_unexpected( + fmt::format("input bytes {} must be dividable by elementSize {}.", + inputs.size(), elementSize)); + } + + size_t num_uniforms = faceVertexCounts.size(); + + dst.resize(num_uniforms * elementSize); + + size_t fvIndexOffset{0}; + + for (size_t i = 0; i < faceVertexCounts.size(); i++) { + size_t cnt = faceVertexCounts[i]; + + if ((fvIndexOffset + cnt) > faceVertexIndices.size()) { + return nonstd::make_unexpected( + fmt::format("faceVertexCounts[{}] {} gives buffer-overrun to " + "faceVertexIndices.size {}.", + i, cnt, faceVertexIndices.size())); + } + + for (size_t k = 0; k < cnt; k++) { + uint32_t v_idx = faceVertexIndices[fvIndexOffset + k]; + + if (v_idx >= inputs.size()) { + return nonstd::make_unexpected( + fmt::format("vertexIndex {} is out-of-range for inputs.size {}.", + v_idx, inputs.size())); + } + + // may overwrite the value + memcpy(&dst[v_idx * elementSize], &inputs[i * elementSize], + sizeof(T) * elementSize); + } + + fvIndexOffset += cnt; + } + + return dst; +} +#endif + +nonstd::expected, std::string> UniformToVertex( + const std::vector &inputs, const size_t stride_bytes, + const std::vector &faceVertexCounts, + const std::vector &faceVertexIndices) { + std::vector dst; + + if (stride_bytes == 0) { + return nonstd::make_unexpected(fmt::format("stride_bytes is zero.")); + } + + if (faceVertexIndices.size() < 3) { + return nonstd::make_unexpected( + fmt::format("faceVertexIndices.size must be 3 or greater, but got {}.", + faceVertexCounts.size())); + } + + if ((inputs.size() % stride_bytes) != 0) { + return nonstd::make_unexpected( + fmt::format("input bytes {} must be dividable by stride_bytes {}.", + inputs.size(), stride_bytes)); + } + + size_t num_uniforms = inputs.size() / stride_bytes; + + if (num_uniforms == faceVertexCounts.size()) { + return nonstd::make_unexpected(fmt::format( + "The number of input uniform attributes {} must be the same with " + "faceVertexCounts.size() {}", + num_uniforms, faceVertexCounts.size())); + } + + dst.resize(num_uniforms * stride_bytes); + + size_t fvIndexOffset{0}; + + for (size_t i = 0; i < faceVertexCounts.size(); i++) { + size_t cnt = faceVertexCounts[i]; + + if ((fvIndexOffset + cnt) > faceVertexIndices.size()) { + return nonstd::make_unexpected( + fmt::format("faceVertexCounts[{}] {} gives buffer-overrun to " + "faceVertexIndices.size {}.", + i, cnt, faceVertexIndices.size())); + } + + for (size_t k = 0; k < cnt; k++) { + uint32_t v_idx = faceVertexIndices[fvIndexOffset + k]; + + if (v_idx >= inputs.size()) { + return nonstd::make_unexpected( + fmt::format("vertexIndex {} is out-of-range for inputs.size {}.", + v_idx, inputs.size())); + } + + // may overwrite the value + memcpy(dst.data() + v_idx * stride_bytes, + inputs.data() + i * stride_bytes, stride_bytes); + } + + fvIndexOffset += cnt; + } + + return dst; +} + +// Generic uniform to facevarying conversion +nonstd::expected, std::string> UniformToFaceVarying( + const std::vector &src, const size_t stride_bytes, + const std::vector &faceVertexCounts) { + std::vector dst; + + if (stride_bytes == 0) { + return nonstd::make_unexpected("stride_bytes is zero."); + } + + if ((src.size() % stride_bytes) != 0) { + return nonstd::make_unexpected( + fmt::format("input bytes {} must be the multiple of stride_bytes {}", + src.size(), stride_bytes)); + } + + size_t num_uniforms = src.size() / stride_bytes; + + if (num_uniforms != faceVertexCounts.size()) { + return nonstd::make_unexpected(fmt::format( + "The number of input uniform attributes {} must be the same with " + "faceVertexCounts.size() {}", + num_uniforms, faceVertexCounts.size())); + } + + std::vector buf; + buf.resize(stride_bytes); + + for (size_t i = 0; i < faceVertexCounts.size(); i++) { + size_t cnt = faceVertexCounts[i]; + + memcpy(buf.data(), src.data() + i * stride_bytes, stride_bytes); + + // repeat cnt times. + for (size_t k = 0; k < cnt; k++) { + dst.insert(dst.end(), buf.begin(), buf.end()); + } + } + + return dst; +} + +// +// Convert vertex attribute with Vertex variability(interpolation) to +// facevarying attribute, by expanding(flatten) the value per vertex per face. +// +#if 0 // unsued atm +template +nonstd::expected, std::string> VertexToFaceVarying( + const std::vector &inputs, const std::vector &faceVertexCounts, + const std::vector &faceVertexIndices) { + std::vector dst; + + size_t face_offset{0}; + for (size_t i = 0; i < faceVertexCounts.size(); i++) { + size_t cnt = faceVertexCounts[i]; + + for (size_t k = 0; k < cnt; k++) { + size_t idx = k + face_offset; + + if (idx >= faceVertexIndices.size()) { + return nonstd::make_unexpected(fmt::format( + "faeVertexIndex out-of-range at faceVertexCount[{}]", i)); + } + + size_t v_idx = faceVertexIndices[idx]; + + if (v_idx >= inputs.size()) { + return nonstd::make_unexpected( + fmt::format("faeVertexIndices[{}] {} exceeds input array size {}", + idx, v_idx, inputs.size())); + } + + dst.emplace_back(inputs[v_idx]); + } + + face_offset += cnt; + } + + return dst; +} +#endif + +// Generic vertex to facevarying conversion +nonstd::expected, std::string> VertexToFaceVarying( + const std::vector &src, const size_t stride_bytes, + const std::vector &faceVertexCounts, + const std::vector &faceVertexIndices) { + std::vector dst; + + if (src.empty()) { + return nonstd::make_unexpected("src data is empty."); + } + + if (stride_bytes == 0) { + return nonstd::make_unexpected("stride_bytes must be non-zero."); + } + + if ((src.size() % stride_bytes) != 0) { + return nonstd::make_unexpected( + fmt::format("src size {} must be the multiple of stride_bytes {}", + src.size(), stride_bytes)); + } + + const size_t num_vertices = src.size() / stride_bytes; + + std::vector buf; + buf.resize(stride_bytes); + + size_t faceVertexIndexOffset{0}; + + for (size_t i = 0; i < faceVertexCounts.size(); i++) { + size_t cnt = faceVertexCounts[i]; + + for (size_t k = 0; k < cnt; k++) { + size_t fv_idx = k + faceVertexIndexOffset; + + if (fv_idx >= faceVertexIndices.size()) { + return nonstd::make_unexpected( + fmt::format("faeVertexIndex {} out-of-range at faceVertexCount[{}]", + fv_idx, i)); + } + + size_t v_idx = faceVertexIndices[fv_idx]; + + if (v_idx >= num_vertices) { + return nonstd::make_unexpected(fmt::format( + "faeVertexIndices[{}] {} exceeds the number of vertices {}", fv_idx, + v_idx, num_vertices)); + } + + memcpy(buf.data(), src.data() + v_idx * stride_bytes, stride_bytes); + dst.insert(dst.end(), buf.begin(), buf.end()); + } + + faceVertexIndexOffset += cnt; + } + + return dst; +} + +#if 0 // unused a.t.m +// Copy single value to facevarying vertices. +template +static nonstd::expected, std::string> ConstantToFaceVarying( + const T &input, const std::vector &faceVertexCounts) { + std::vector dst; + + for (size_t i = 0; i < faceVertexCounts.size(); i++) { + size_t cnt = faceVertexCounts[i]; + + for (size_t k = 0; k < cnt; k++) { + dst.emplace_back(input); + } + } + + return dst; +} +#endif + +static nonstd::expected, std::string> ConstantToVertex( + const std::vector &src, const size_t stride_bytes, + const std::vector &faceVertexCounts, + const std::vector &faceVertexIndices) { + if (faceVertexCounts.empty()) { + return nonstd::make_unexpected("faceVertexCounts is empty."); + } + + if (faceVertexIndices.size() < 3) { + return nonstd::make_unexpected( + fmt::format("faceVertexIndices.size must be at least 3, but got {}.", + faceVertexIndices.size())); + } + + const uint32_t num_vertices = + *std::max_element(faceVertexIndices.cbegin(), faceVertexIndices.cend()); + + std::vector dst; + + if (src.empty()) { + return nonstd::make_unexpected("src data is empty."); + } + + if (stride_bytes == 0) { + return nonstd::make_unexpected("stride_bytes must be non-zero."); + } + + if (src.size() != stride_bytes) { + return nonstd::make_unexpected( + fmt::format("src size {} must be equal to stride_bytes {}", src.size(), + stride_bytes)); + } + + dst.resize(stride_bytes * num_vertices); + + size_t faceVertexIndexOffset = 0; + for (size_t i = 0; i < faceVertexCounts.size(); i++) { + uint32_t cnt = faceVertexCounts[i]; + if (cnt < 3) { + return nonstd::make_unexpected(fmt::format( + "faeVertexCounts[{}] must be equal to or greater than 3, but got {}", + i, cnt)); + } + + for (size_t k = 0; k < cnt; k++) { + size_t fv_idx = k + faceVertexIndexOffset; + + if (fv_idx >= faceVertexIndices.size()) { + return nonstd::make_unexpected( + fmt::format("faeVertexIndex {} out-of-range at faceVertexCount[{}]", + fv_idx, i)); + } + + size_t v_idx = faceVertexIndices[fv_idx]; + + if (v_idx >= num_vertices) { // this should not happen. just in case. + return nonstd::make_unexpected(fmt::format( + "faeVertexIndices[{}] {} exceeds the number of vertices {}", fv_idx, + v_idx, num_vertices)); + } + + memcpy(dst.data() + v_idx * stride_bytes, src.data(), stride_bytes); + } + + faceVertexIndexOffset += cnt; + } + + return dst; +} + +#if 0 +static nonstd::expected, std::string> +ConstantToFaceVarying(const std::vector &src, + const size_t stride_bytes, + const std::vector &faceVertexCounts) { + std::vector dst; + + if (src.empty()) { + return nonstd::make_unexpected("src data is empty."); + } + + if (stride_bytes == 0) { + return nonstd::make_unexpected("stride_bytes must be non-zero."); + } + + if ((src.size() != stride_bytes)) { + return nonstd::make_unexpected( + fmt::format("src size {} must be equal to stride_bytes {}", src.size(), + stride_bytes)); + } + + std::vector buf; + buf.resize(stride_bytes); + + for (size_t i = 0; i < faceVertexCounts.size(); i++) { + size_t cnt = faceVertexCounts[i]; + + for (size_t k = 0; k < cnt; k++) { + dst.insert(dst.end(), buf.begin(), buf.end()); + } + } + + return dst; +} +#endif + +// T = int +template +bool TryConvertFacevaryingToVertexInt( + const std::vector &src, std::vector *dst, + const std::vector &faceVertexIndices) { + if (!dst) { + return false; + } + + if (src.size() != faceVertexIndices.size()) { + return false; + } + + // size must be at least 1 triangle(3 verts). + if (faceVertexIndices.size() < 3) { + return false; + } + + // vidx, value + std::unordered_map vdata; + + uint32_t max_vidx = 0; + for (size_t i = 0; i < faceVertexIndices.size(); i++) { + uint32_t vidx = faceVertexIndices[i]; + max_vidx = (std::max)(vidx, max_vidx); + + if (vdata.count(vidx)) { + if (!math::is_close(vdata[vidx], src[i])) { + return false; + } + } else { + vdata[vidx] = src[i]; + } + } + + dst->resize(max_vidx + 1); + memset(dst->data(), 0, (max_vidx + 1) * sizeof(T)); + + for (const auto &v : vdata) { + (*dst)[v.first] = v.second; + } + + return true; +} + +// T = float, double, float2, ... +template +bool TryConvertFacevaryingToVertexFloat( + const std::vector &src, std::vector *dst, + const std::vector &faceVertexIndices, const EpsTy eps) { + DCOUT("TryConvertFacevaryingToVertexFloat"); + if (!dst) { + return false; + } + + if (src.size() != faceVertexIndices.size()) { + DCOUT("size mismatch."); + return false; + } + + // size must be at least 1 triangle(3 verts). + if (faceVertexIndices.size() < 3) { + return false; + } + + // vidx, value + std::unordered_map vdata; + + uint32_t max_vidx = 0; + for (size_t i = 0; i < faceVertexIndices.size(); i++) { + uint32_t vidx = faceVertexIndices[i]; + max_vidx = (std::max)(vidx, max_vidx); + + if (vdata.count(vidx)) { + if (!math::is_close(vdata[vidx], src[i], eps)) { + DCOUT("diff at faceVertexIndices[" << i << "]"); + return false; + } + } else { + vdata[vidx] = src[i]; + } + } + + dst->resize(max_vidx + 1); + memset(dst->data(), 0, (max_vidx + 1) * sizeof(T)); + + for (const auto &v : vdata) { + (*dst)[v.first] = v.second; + } + + return true; +} + +// T = matrix type. +template +bool TryConvertFacevaryingToVertexMat( + const std::vector &src, std::vector *dst, + const std::vector &faceVertexIndices) { + if (!dst) { + return false; + } + + if (src.size() != faceVertexIndices.size()) { + return false; + } + + // size must be at least 1 triangle(3 verts). + if (faceVertexIndices.size() < 3) { + return false; + } + + // vidx, value + std::unordered_map vdata; + + uint32_t max_vidx = 0; + for (size_t i = 0; i < faceVertexIndices.size(); i++) { + uint32_t vidx = faceVertexIndices[i]; + max_vidx = (std::max)(vidx, max_vidx); + + if (vdata.count(vidx)) { + if (!is_close(vdata[vidx], src[i])) { + return false; + } + } else { + vdata[vidx] = src[i]; + } + } + + dst->assign(max_vidx + 1, T::identity()); + + for (const auto &v : vdata) { + (*dst)[v.first] = v.second; + } + + return true; +} + +/// +/// Try to convert 'facevarying' vertex attribute to 'vertex' attribute. +/// Inspect each vertex value is the same(with given eps) +/// +/// Current limitation: +/// - stride must be 0 or tightly packed. +/// - elementSize must be 1 +/// +/// @return true when 'facevarying' vertex attribute successfully converted to +/// 'vertex' +/// +static bool TryConvertFacevaryingToVertex( + const VertexAttribute &src, VertexAttribute *dst, + const std::vector &faceVertexIndices, std::string *err, + const float eps) { + DCOUT("TryConvertFacevaryingToVertex"); + if (!dst) { + PUSH_ERROR_AND_RETURN("Output `dst` is nullptr."); + } + + if (!src.is_facevarying()) { + PUSH_ERROR_AND_RETURN("Input must be 'facevarying' attribute"); + } + + if (src.element_size() != 1) { + PUSH_ERROR_AND_RETURN("Input's element_size must be 1."); + } + + if ((src.stride != 0) && (src.stride_bytes() != src.format_size())) { + PUSH_ERROR_AND_RETURN( + "Input attribute must be tightly packed. stride_bytes = " + << src.stride_bytes() << ", format_size = " << src.format_size()); + } + +#define CONVERT_FUN_INT(__fmt, __ty) \ + if (src.format == __fmt) { \ + std::vector<__ty> vsrc; \ + vsrc.resize(src.vertex_count()); \ + memcpy(vsrc.data(), src.get_data().data(), src.get_data().size()); \ + std::vector<__ty> vdst; \ + bool ret = TryConvertFacevaryingToVertexInt<__ty>(vsrc, &vdst, \ + faceVertexIndices); \ + if (!ret) { \ + return false; \ + } \ + dst->elementSize = 1; \ + dst->format = src.format; \ + dst->variability = VertexVariability::Vertex; \ + dst->data.resize(vdst.size() * src.format_size()); \ + memcpy(dst->data.data(), vdst.data(), dst->data.size()); \ + return true; \ + } else + +#define CONVERT_FUN_FLOAT(__fmt, __ty, __epsty) \ + if (src.format == __fmt) { \ + std::vector<__ty> vsrc; \ + vsrc.resize(src.vertex_count()); \ + memcpy(vsrc.data(), src.get_data().data(), src.get_data().size()); \ + std::vector<__ty> vdst; \ + bool ret = TryConvertFacevaryingToVertexFloat<__ty, __epsty>( \ + vsrc, &vdst, faceVertexIndices, __epsty(eps)); \ + if (!ret) { \ + return false; \ + } \ + dst->elementSize = 1; \ + dst->format = src.format; \ + dst->variability = VertexVariability::Vertex; \ + dst->data.resize(vdst.size() * src.format_size()); \ + memcpy(dst->data.data(), vdst.data(), dst->data.size()); \ + return true; \ + } else + +#define CONVERT_FUN_MAT(__fmt, __ty) \ + if (src.format == __fmt) { \ + std::vector<__ty> vsrc; \ + vsrc.resize(src.vertex_count()); \ + memcpy(vsrc.data(), src.get_data().data(), src.get_data().size()); \ + std::vector<__ty> vdst; \ + bool ret = TryConvertFacevaryingToVertexMat<__ty>(vsrc, &vdst, \ + faceVertexIndices); \ + if (!ret) { \ + return false; \ + } \ + dst->elementSize = 1; \ + dst->format = src.format; \ + dst->variability = VertexVariability::Vertex; \ + dst->data.resize(vdst.size() * src.format_size()); \ + memcpy(dst->data.data(), vdst.data(), dst->data.size()); \ + return true; \ + } else + + // NOTE: VertexAttributeFormat::Bool is preserved + CONVERT_FUN_INT(VertexAttributeFormat::Bool, uint8_t) + CONVERT_FUN_FLOAT(VertexAttributeFormat::Float, float, float) + CONVERT_FUN_FLOAT(VertexAttributeFormat::Vec2, value::float2, float) + CONVERT_FUN_FLOAT(VertexAttributeFormat::Vec3, value::float3, float) + CONVERT_FUN_FLOAT(VertexAttributeFormat::Vec4, value::float4, float) + CONVERT_FUN_INT(VertexAttributeFormat::Char, signed char) + // CONVERT_FUN(VertexAttributeFormat::Char2, value::char2) + // CONVERT_FUN(VertexAttributeFormat::Char3, value::char3) + // CONVERT_FUN(VertexAttributeFormat::Char4, // int8x4 + CONVERT_FUN_INT(VertexAttributeFormat::Byte, uint8_t) + // CONVERT_FUN(VertexAttributeFormat::Byte2, // uint8x2 + // CONVERT_FUN(VertexAttributeFormat::Byte3, // uint8x3 + // CONVERT_FUN(VertexAttributeFormat::Byte4, // uint8x4 + CONVERT_FUN_INT(VertexAttributeFormat::Short, int16_t) + // CONVERT_FUN(VertexAttributeFormat::Short2, value::short2) + // CONVERT_FUN(VertexAttributeFormat::Short3, value::short3) + // CONVERT_FUN(VertexAttributeFormat::Short4, value::short4) + CONVERT_FUN_INT(VertexAttributeFormat::Ushort, uint16_t) + // CONVERT_FUN(VertexAttributeFormat::Ushort2, uint16_t) + // CONVERT_FUN(VertexAttributeFormat::Ushort3, uint16_t) + // CONVERT_FUN(VertexAttributeFormat::Ushort4, uint16_t) + CONVERT_FUN_FLOAT(VertexAttributeFormat::Half, value::half, float) + CONVERT_FUN_FLOAT(VertexAttributeFormat::Half2, value::half2, float) + CONVERT_FUN_FLOAT(VertexAttributeFormat::Half3, value::half3, float) + CONVERT_FUN_FLOAT(VertexAttributeFormat::Half4, value::half4, float) + CONVERT_FUN_INT(VertexAttributeFormat::Int, int) + CONVERT_FUN_INT(VertexAttributeFormat::Ivec2, value::int2) + CONVERT_FUN_INT(VertexAttributeFormat::Ivec3, value::int3) + CONVERT_FUN_INT(VertexAttributeFormat::Ivec4, value::int4) + CONVERT_FUN_INT(VertexAttributeFormat::Uint, uint32_t) + CONVERT_FUN_INT(VertexAttributeFormat::Uvec2, value::uint2) + CONVERT_FUN_INT(VertexAttributeFormat::Uvec3, value::uint3) + CONVERT_FUN_INT(VertexAttributeFormat::Uvec4, value::uint4) + // NOTE: Use float precision eps is upcasted to double precision. + CONVERT_FUN_FLOAT(VertexAttributeFormat::Double, double, double) + CONVERT_FUN_FLOAT(VertexAttributeFormat::Dvec2, value::double2, double) + CONVERT_FUN_FLOAT(VertexAttributeFormat::Dvec3, value::double3, double) + CONVERT_FUN_FLOAT(VertexAttributeFormat::Dvec4, value::double4, double) + CONVERT_FUN_MAT(VertexAttributeFormat::Mat2, value::matrix2f) + CONVERT_FUN_MAT(VertexAttributeFormat::Mat3, value::matrix3f) + CONVERT_FUN_MAT(VertexAttributeFormat::Mat4, value::matrix4f) + CONVERT_FUN_MAT(VertexAttributeFormat::Dmat2, value::matrix2d) + CONVERT_FUN_MAT(VertexAttributeFormat::Dmat3, value::matrix3d) + CONVERT_FUN_MAT(VertexAttributeFormat::Dmat4, value::matrix4d) { + if (err) { + (*err) += + fmt::format("Unsupported/Unimplemented VertexAttributeFormat: {}", + to_string(src.format)); + } + } + +#undef CONVERT_FUN_INT +#undef CONVERT_FUN_FLOAT +#undef CONVERT_FUN_MAT + + return false; +} + +#if 0 // Not used atm. +static bool ToFaceVaryingAttribute(const std::string &attr_name, + const VertexAttribute &src, + const std::vector &faceVertexCounts, + const std::vector &faceVertexIndices, + VertexAttribute *dst, + std::string *err) { + +#define PushError(msg) \ + if (err) { \ + (*err) += msg; \ + } + + if (!dst) { + PUSH_ERROR_AND_RETURN("'dest' parameter is nullptr."); + } + + if (src.variability == VertexVariability::Indexed) { + PUSH_ERROR_AND_RETURN(fmt::format("'indexed' variability for {} is not supported.", attr_name)); + } else if (src.variability == VertexVariability::Constant) { + + auto result = ConstantToFaceVarying(src.get_data(), src.stride_bytes(), + faceVertexCounts); + + if (!result) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to convert vertex data with 'constant' variability to 'facevarying': name {}.", attr_name)); + } + + dst->data = result.value(); + dst->elementSize = src.elementSize; + dst->format = src.format; + dst->stride = src.stride; + dst->variability = VertexVariability::FaceVarying; + + return true; + + } else if (src.variability == VertexVariability::Uniform) { + + auto result = UniformToFaceVarying(src.get_data(), src.stride_bytes(), + faceVertexCounts); + + if (!result) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to convert vertex data with 'uniform' variability to 'facevarying': name {}.", attr_name)); + } + + dst->data = result.value(); + dst->elementSize = src.elementSize; + dst->format = src.format; + dst->stride = src.stride; + dst->variability = VertexVariability::FaceVarying; + + return true; + + } else if (src.variability == VertexVariability::Vertex) { + + auto result = VertexToFaceVarying(src.get_data(), src.stride_bytes(), + faceVertexCounts, faceVertexIndices); + + if (!result) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to convert vertex data with 'vertex' variability to 'facevarying': name {}.", attr_name)); + } + + dst->data = result.value(); + dst->elementSize = src.elementSize; + dst->format = src.format; + dst->stride = src.stride; + dst->variability = VertexVariability::FaceVarying; + + return true; + } else if (src.variability == VertexVariability::FaceVarying) { + (*dst) = src; + return true; + } + +#undef PushError + + return false; +} + +static bool ToVertexVaryingAttribute( + const std::string &attr_name, + const VertexAttribute &src, + const std::vector &faceVertexCounts, + const std::vector &faceVertexIndices, + VertexAttribute *dst, + std::string *err) { + +#define PushError(msg) \ + if (err) { \ + (*err) += msg; \ + } + + if (!dst) { + PUSH_ERROR_AND_RETURN("'dest' parameter is nullptr."); + } + + if (src.variability == VertexVariability::Indexed) { + PUSH_ERROR_AND_RETURN(fmt::format("'indexed' variability for {} is not supported.", attr_name)); + } else if (src.variability == VertexVariability::Constant) { + + auto result = ConstantToVertex(src.get_data(), src.stride_bytes(), + faceVertexCounts, faceVertexIndices); + + if (!result) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to convert vertex data with 'constant' variability to 'facevarying': name {}.", attr_name)); + } + + dst->data = result.value(); + dst->elementSize = src.elementSize; + dst->format = src.format; + dst->stride = src.stride; + dst->variability = VertexVariability::Vertex; + + return true; + + } else if (src.variability == VertexVariability::Uniform) { + + auto result = UniformToVertex(src.get_data(), src.stride_bytes(), + faceVertexCounts, faceVertexIndices); + + if (!result) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to convert vertex data with 'uniform' variability to 'facevarying': name {}.", attr_name)); + } + + dst->data = result.value(); + dst->elementSize = src.elementSize; + dst->format = src.format; + dst->stride = src.stride; + dst->variability = VertexVariability::Vertex; + + return true; + + } else if (src.variability == VertexVariability::Vertex) { + + (*dst) = src; + return true; + } else if (src.variability == VertexVariability::FaceVarying) { + + PUSH_ERROR_AND_RETURN(fmt::format("'facevarying' variability cannot be converted to 'vertex' variability: name {}.", attr_name)); + + } + +#undef PushError + + return false; + +} +#endif + +/// +/// Triangulate VeretexAttribute data. +/// +static bool TriangulateVertexAttribute( + VertexAttribute &vattr, const std::vector &faceVertexCounts, + const std::vector &triangulatedToOrigFaceVertexIndexMap, + const std::vector &triangulatedFaceCounts, + const std::vector &triangulatedFaceVertexIndices, + std::string *err) { + if (vattr.vertex_count() == 0) { + return true; + } + + if (triangulatedFaceCounts.empty()) { + PUSH_ERROR_AND_RETURN("triangulatedFaceCounts is empty."); + } + + if (faceVertexCounts.size() != triangulatedFaceCounts.size()) { + PUSH_ERROR_AND_RETURN( + "faceVertexCounts.size must be equal to triangulatedFaceCounts.size."); + } + + if ((triangulatedFaceVertexIndices.size() % 3) != 0) { + PUSH_ERROR_AND_RETURN("Invalid size for triangulatedFaceVertexIndices."); + } + + if (vattr.is_facevarying()) { + if (triangulatedToOrigFaceVertexIndexMap.size() != + triangulatedFaceVertexIndices.size()) { + PUSH_ERROR_AND_RETURN( + "triangulatedToOrigFaceVertexIndexMap.size must be equal to " + "triangulatedFaceVertexIndices."); + } + + size_t num_vs = vattr.vertex_count(); + std::vector buf; + + for (uint32_t f = 0; f < triangulatedFaceVertexIndices.size(); f++) { + // Array index to faceVertexIndices(before triangulation). + size_t src_fvIdx = triangulatedToOrigFaceVertexIndexMap[f]; + + if (src_fvIdx >= num_vs) { + PUSH_ERROR_AND_RETURN( + "Invalid index found in triangulatedFaceVertexIndices."); + } + + buf.insert( + buf.end(), vattr.get_data().data() + src_fvIdx * vattr.stride_bytes(), + vattr.get_data().data() + (1 + src_fvIdx) * vattr.stride_bytes()); + } + + vattr.data = std::move(buf); + } else if (vattr.is_vertex()) { + // # of vertices does not change, so nothing is required. + return true; + } else if (vattr.is_indexed()) { + PUSH_ERROR_AND_RETURN("Indexed VertexAttribute is not supported."); + } else if (vattr.is_constant()) { + std::vector buf; + + for (size_t f = 0; f < triangulatedFaceCounts.size(); f++) { + uint32_t nf = triangulatedFaceCounts[f]; + + // copy `nf` times. + for (size_t k = 0; k < nf; k++) { + buf.insert(buf.end(), + vattr.get_data().data() + f * vattr.stride_bytes(), + vattr.get_data().data() + (1 + f) * vattr.stride_bytes()); + } + } + + vattr.data = std::move(buf); + } else if (vattr.is_uniform()) { + // nothing is required + return true; + } + + return true; +} + +std::vector GetMaterialBindGeomSubsets( + const tinyusdz::Prim &prim) { + std::vector dst; + + // GeomSubet Prim must be a child Prim of GeomMesh. + for (const auto &child : prim.children()) { + if (const tinyusdz::GeomSubset *psubset = + child.as()) { + value::token tok; + if (!psubset->familyName.get_value(&tok)) { + continue; + } + + if (tok.str() != "materialBind") { + continue; + } + + dst.push_back(psubset); + } + } + + return dst; +} + +// +// name does not include "primvars:" prefix. +// TODO: connected attribute. +// +nonstd::expected GetTextureCoordinate( + const Stage &stage, const GeomMesh &mesh, const std::string &name, + const double t, const value::TimeSampleInterpolationType tinterp) { + VertexAttribute vattr; + + (void)stage; + + std::string err; + GeomPrimvar primvar; + if (!GetGeomPrimvar(stage, &mesh, name, &primvar, &err)) { + return nonstd::make_unexpected(err); + } + + if (!primvar.has_value()) { + return nonstd::make_unexpected("No value exist for primvars:" + name + + "\n"); + } + + // TODO: allow float2? + if (primvar.get_type_id() != + value::TypeTraits>::type_id()) { + return nonstd::make_unexpected( + "Texture coordinate primvar must be texCoord2f[] type, but got " + + primvar.get_type_name() + "\n"); + } + + if (primvar.get_interpolation() == Interpolation::Varying) { + vattr.variability = VertexVariability::Varying; + } else if (primvar.get_interpolation() == Interpolation::Constant) { + vattr.variability = VertexVariability::Constant; + } else if (primvar.get_interpolation() == Interpolation::Uniform) { + vattr.variability = VertexVariability::Uniform; + } else if (primvar.get_interpolation() == Interpolation::Vertex) { + vattr.variability = VertexVariability::Vertex; + } else if (primvar.get_interpolation() == Interpolation::FaceVarying) { + vattr.variability = VertexVariability::FaceVarying; + } + + std::vector uvs; + if (!primvar.flatten_with_indices(t, &uvs, tinterp)) { + return nonstd::make_unexpected( + "Failed to retrieve texture coordinate primvar with concrete type.\n"); + } + + vattr.format = VertexAttributeFormat::Vec2; + vattr.data.resize(uvs.size() * sizeof(value::texcoord2f)); + memcpy(vattr.data.data(), uvs.data(), vattr.data.size()); + vattr.indices.clear(); // just in case. + + vattr.name = name; // TODO: add "primvars:" namespace? + + return std::move(vattr); +} + +#if 0 // not used at the moment. +/// +/// For GeomSubset. Build offset table to corresponding array index in +/// mesh.faceVertexIndices. No need to use this function for triangulated mesh, +/// since the index can be easily computed as `3 * subset.indices[i]` +/// +bool BuildFaceVertexIndexOffsets(const std::vector &faceVertexCounts, + std::vector &faceVertexIndexOffsets) { + size_t offset = 0; + for (size_t i = 0; i < faceVertexCounts.size(); i++) { + uint32_t npolys = faceVertexCounts[i]; + + faceVertexIndexOffsets.push_back(offset); + offset += npolys; + } + + return true; +} +#endif + +namespace { + +template +bool ScalarValueToVertexAttribute(const value::Value &value, + const std::string &name, + const VertexAttributeFormat format, + VertexAttribute &dst, std::string *err) { + if (VertexAttributeFormatSize(format) != sizeof(UnderlyingTy)) { + PUSH_ERROR_AND_RETURN("format size mismatch."); + return false; + } + + if (auto pv = value.as()) { + dst.data.resize(sizeof(UnderlyingTy)); + memcpy(dst.data.data(), pv, sizeof(UnderlyingTy)); + + dst.elementSize = 1; + dst.stride = 0; + dst.format = format; + dst.variability = VertexVariability::Constant; + dst.name = name; + dst.indices.clear(); + return true; + } + + PUSH_ERROR_AND_RETURN("[Internal error] value is not scalar-typed value."); +} + +template +bool ArrayValueToVertexAttribute( + const value::Value &value, const std::string &name, + const uint32_t elementSize, const VertexVariability variability, + const uint32_t num_vertices, const uint32_t num_face_counts, + const uint32_t num_face_vertex_indices, const VertexAttributeFormat format, + VertexAttribute &dst, std::string *err) { + if (!value::TypeTraits::is_array()) { + PUSH_ERROR_AND_RETURN( + "[Internal error] UnderlyingTy template parameter must be array type."); + } + + size_t baseTySize = value::TypeTraits::size(); + + size_t value_counts = value.array_size(); + if (value_counts == 0) { + PUSH_ERROR_AND_RETURN("Empty array size"); + } + + if (variability == VertexVariability::Indexed) { + PUSH_ERROR_AND_RETURN("Indexed variability is not supported."); + } + + if (VertexAttributeFormatSize(format) != baseTySize) { + PUSH_ERROR_AND_RETURN("format size mismatch. expected " + << VertexAttributeFormatSize(format) << " but got " + << baseTySize); + return false; + } + + DCOUT("value.type = " << value.type_name()); + DCOUT("UnderlyingTy = " << value::TypeTraits::type_name()); + const auto p = value.as(); + if (!p) { + DCOUT("p is nullptr"); + } + + if (auto pv = value.as()) { + if (variability == VertexVariability::Constant) { + if (value_counts != elementSize) { + PUSH_ERROR_AND_RETURN(fmt::format( + "# of items {} expected, but got {}. Variability = Constant", + elementSize, value_counts)); + } + } else if (variability == VertexVariability::Uniform) { + if (value_counts != (elementSize * num_face_counts)) { + PUSH_ERROR_AND_RETURN(fmt::format( + "# of items {} expected, but got {}. Variability = Uniform", + elementSize * num_face_counts, value_counts)); + } + } else if (variability == VertexVariability::Vertex) { + if (value_counts != (elementSize * num_vertices)) { + PUSH_ERROR_AND_RETURN(fmt::format( + "# of items {} expected, but got {}. Variability = Vertex", + elementSize * num_vertices, value_counts)); + } + } else { // facevarying + if (value_counts != (elementSize * num_face_vertex_indices)) { + PUSH_ERROR_AND_RETURN(fmt::format( + "# of items {} expected, but got {}. Variability = FaceVarying", + elementSize * num_face_vertex_indices, value_counts)); + } + } + + dst.data.resize(value_counts * baseTySize); + memcpy(dst.data.data(), pv->data(), value_counts * baseTySize); + + dst.elementSize = elementSize; + dst.stride = 0; + dst.format = format; + dst.variability = variability; + dst.name = name; + dst.indices.clear(); + return true; + } + + PUSH_ERROR_AND_RETURN(fmt::format( + "Requested underlying type {} but input `value` has underlying type {}.", + value::TypeTraits::type_name(), + value.underlying_type_name())); +} + +} // namespace + +bool ToVertexAttribute(const GeomPrimvar &primvar, const std::string &name, + const uint32_t num_vertices, + const uint32_t num_face_counts, + const uint32_t num_face_vertex_indices, + VertexAttribute &dst, std::string *err, const double t, + const value::TimeSampleInterpolationType tinterp) { + uint32_t elementSize = uint32_t(primvar.get_elementSize()); + if (elementSize == 0) { + PUSH_ERROR_AND_RETURN( + fmt::format("elementSize is zero for primvar: {}", primvar.name())); + } + + VertexAttribute vattr; + + const tinyusdz::Attribute &attr = primvar.get_attribute(); + + value::Value value; + if (!primvar.flatten_with_indices(t, &value, tinterp)) { + PUSH_ERROR_AND_RETURN("Failed to flatten primvar"); + } + + bool is_array = value.type_id() & value::TYPE_ID_1D_ARRAY_BIT; + DCOUT("is_array " << (is_array ? "true" : "false")); + + VertexVariability variability; + if (primvar.get_interpolation() == Interpolation::Varying) { + variability = VertexVariability::Varying; + } else if (primvar.get_interpolation() == Interpolation::Constant) { + variability = VertexVariability::Constant; + } else if (primvar.get_interpolation() == Interpolation::Uniform) { + variability = VertexVariability::Uniform; + } else if (primvar.get_interpolation() == Interpolation::Vertex) { + variability = VertexVariability::Vertex; + } else if (primvar.get_interpolation() == Interpolation::FaceVarying) { + variability = VertexVariability::FaceVarying; + } else { + PUSH_ERROR_AND_RETURN("[Internal Error] Invalid `interpolation` type."); + } + + uint32_t baseUnderlyingTypeId = + value.underlying_type_id() & (~value::TYPE_ID_1D_ARRAY_BIT); + DCOUT("flattened primvar type: " << value.type_name() << ", underlying type " + << value::GetTypeName(baseUnderlyingTypeId)); + + // Cast to underlying type + +#define TO_TYPED_VALUE(__underlying_ty, __vfmt) \ + if (baseUnderlyingTypeId == value::TypeTraits<__underlying_ty>::type_id()) { \ + if (is_array) { \ + return ArrayValueToVertexAttribute>( \ + value, name, elementSize, variability, num_vertices, \ + num_face_counts, num_face_vertex_indices, __vfmt, dst, err); \ + } else { \ + return ScalarValueToVertexAttribute<__underlying_ty>(value, name, \ + __vfmt, dst, err); \ + } \ + } else + + // specialization for bool type: bool is represented as uint8 in USD primvar + if (baseUnderlyingTypeId == value::TypeTraits::type_id()) { + if (is_array) { + return ArrayValueToVertexAttribute>( + value, name, elementSize, variability, num_vertices, num_face_counts, + num_face_vertex_indices, VertexAttributeFormat::Bool, dst, err); + } else { + return ScalarValueToVertexAttribute( + value, name, VertexAttributeFormat::Bool, dst, err); + } + } else + TO_TYPED_VALUE(uint8_t, VertexAttributeFormat::Byte) + TO_TYPED_VALUE(value::uchar2, VertexAttributeFormat::Byte2) + TO_TYPED_VALUE(value::uchar3, VertexAttributeFormat::Byte3) + TO_TYPED_VALUE(value::uchar4, VertexAttributeFormat::Byte4) + TO_TYPED_VALUE(char, VertexAttributeFormat::Char) + TO_TYPED_VALUE(value::char2, VertexAttributeFormat::Char2) + TO_TYPED_VALUE(value::char3, VertexAttributeFormat::Char3) + TO_TYPED_VALUE(value::char4, VertexAttributeFormat::Char4) + TO_TYPED_VALUE(short, VertexAttributeFormat::Short) + TO_TYPED_VALUE(value::short2, VertexAttributeFormat::Short2) + TO_TYPED_VALUE(value::short3, VertexAttributeFormat::Short3) + TO_TYPED_VALUE(value::short4, VertexAttributeFormat::Short4) + TO_TYPED_VALUE(uint16_t, VertexAttributeFormat::Ushort) + TO_TYPED_VALUE(value::ushort2, VertexAttributeFormat::Ushort2) + TO_TYPED_VALUE(value::ushort3, VertexAttributeFormat::Ushort3) + TO_TYPED_VALUE(value::ushort4, VertexAttributeFormat::Ushort4) + TO_TYPED_VALUE(int, VertexAttributeFormat::Int) + TO_TYPED_VALUE(value::int2, VertexAttributeFormat::Ivec2) + TO_TYPED_VALUE(value::int3, VertexAttributeFormat::Ivec3) + TO_TYPED_VALUE(value::int4, VertexAttributeFormat::Ivec4) + TO_TYPED_VALUE(uint32_t, VertexAttributeFormat::Uint) + TO_TYPED_VALUE(value::uint2, VertexAttributeFormat::Uvec2) + TO_TYPED_VALUE(value::uint3, VertexAttributeFormat::Uvec3) + TO_TYPED_VALUE(value::uint4, VertexAttributeFormat::Uvec4) + TO_TYPED_VALUE(float, VertexAttributeFormat::Float) + TO_TYPED_VALUE(value::float2, VertexAttributeFormat::Vec2) + TO_TYPED_VALUE(value::float3, VertexAttributeFormat::Vec3) + TO_TYPED_VALUE(value::float4, VertexAttributeFormat::Vec4) + TO_TYPED_VALUE(value::half, VertexAttributeFormat::Half) + TO_TYPED_VALUE(value::half2, VertexAttributeFormat::Half2) + TO_TYPED_VALUE(value::half3, VertexAttributeFormat::Half3) + TO_TYPED_VALUE(value::half4, VertexAttributeFormat::Half4) + TO_TYPED_VALUE(double, VertexAttributeFormat::Double) + TO_TYPED_VALUE(value::double2, VertexAttributeFormat::Dvec2) + TO_TYPED_VALUE(value::double3, VertexAttributeFormat::Dvec3) + TO_TYPED_VALUE(value::double4, VertexAttributeFormat::Dvec4) + TO_TYPED_VALUE(value::matrix2f, VertexAttributeFormat::Mat2) + TO_TYPED_VALUE(value::matrix3f, VertexAttributeFormat::Mat3) + TO_TYPED_VALUE(value::matrix4f, VertexAttributeFormat::Mat4) + TO_TYPED_VALUE(value::matrix2d, VertexAttributeFormat::Dmat2) + TO_TYPED_VALUE(value::matrix3d, VertexAttributeFormat::Dmat3) + TO_TYPED_VALUE(value::matrix4d, VertexAttributeFormat::Dmat4) { + PUSH_ERROR_AND_RETURN( + fmt::format("Unknown or unsupported data type for Geom PrimVar: {}", + attr.type_name())); + } + +#undef TO_TYPED_VALUE +} + +#if 0 // TODO: Remove. The following could be done using ToVertexAttribute + + // TriangulateVertexAttribute +/// +/// Triangulate Geom primvar. +/// +/// triangulatted indices are computed in `TriangulatePolygon` API. +/// +/// @param[in] mesh Geom mesh +/// @param[in] name Geom Primvar name. +/// @param[in] triangulatedFaceVertexIndices Triangulated faceVertexIndices(len +/// = 3 * triangles) +/// @param[in] triangulatedToOrigFaceVertexIndexMap Triangulated faceVertexIndex +/// to original faceVertexIndex remapping table. len = 3 * triangles. +/// +nonstd::expected TriangulateGeomPrimvar( + const GeomMesh &mesh, const std::string &name, + const std::vector &faceVertexCounts, + const std::vector &faceVertexIndices, + const std::vector &triangulatedFaceVertexIndices, + const std::vector &triangulatedToOrigFaceVertexIndexMap) { + GeomPrimvar primvar; + + if (triangulatedFaceVertexIndices.size() % 3 != 0) { + return nonstd::make_unexpected(fmt::format( + "triangulatedFaceVertexIndices.size {} must be the multiple of 3.\n", + triangulatedFaceVertexIndices.size())); + } + + if (!GetGeomPrimvar(name, &primvar)) { + return nonstd::make_unexpected( + fmt::format("No primvars:{} found in GeomMesh {}\n", name, mesh.name)); + } + + if (!primvar.has_value()) { + // TODO: Create empty VertexAttribute? + return nonstd::make_unexpected( + fmt::format("No value exist for primvars:{}\n", name)); + } + + // + // Flatten Indexed PrimVar(return raw primvar for non-Indexed PrimVar) + // + std::string err; + value::Value flattened; + if (!primvar.flatten_with_indices(t, &flattened, tinterp, &err)) { + return nonstd::make_unexpected(fmt::format( + "Failed to flatten Indexed PrimVar: {}. Error = {}\n", name, err)); + } + + VertexAttribute vattr; + + if (!ToVertexAttributeData(primvar, &vattr, &err)) { + return nonstd::make_unexpected(fmt::format( + "Failed to convert Geom PrimVar to VertexAttribute for {}. Error = {}\n", name, err)); + } + + return vattr; +} +#endif + +#if 1 +/// +/// Input: points, faceVertexCounts, faceVertexIndices +/// Output: triangulated faceVertexCounts(all filled with 3), triangulated +/// faceVertexIndices, triangulatedToOrigFaceVertexIndexMap (length = +/// triangulated faceVertexIndices. triangulatedToOrigFaceVertexIndexMap[i] +/// stores an array index to original faceVertexIndices. For remapping +/// facevarying primvar attributes.) +/// +/// triangulatedFaceVertexCounts: len = len(faceVertexCounts). Records the +/// number of triangle faces. 1 = triangle. 2 = quad, ... For remapping face +/// indices(e.g. GeomSubset::indices) +/// +/// triangulated*** output is generated even when input mesh is fully composed +/// from triangles(`faceVertexCounts` are all filled with 3) Return false when a +/// polygon is degenerated. No overlap check at the moment +/// +/// Example: +/// - faceVertexCounts = [4] +/// - faceVertexIndices = [0, 1, 3, 2] +/// +/// - triangulatedFaceVertexCounts = [3, 3] +/// - triangulatedFaceVertexIndices = [0, 1, 3, 0, 3, 2] +/// - triangulatedToOrigFaceVertexIndexMap = [0, 1, 2, 0, 2, 3] +/// +/// T = value::float3 or value::double3 +/// BaseTy = float or double +template +bool TriangulatePolygon( + const std::vector &points, const std::vector &faceVertexCounts, + const std::vector &faceVertexIndices, + std::vector &triangulatedFaceVertexCounts, + std::vector &triangulatedFaceVertexIndices, + std::vector &triangulatedToOrigFaceVertexIndexMap, + std::vector &triangulatedFaceCounts, std::string &err) { + triangulatedFaceVertexCounts.clear(); + triangulatedFaceVertexIndices.clear(); + + triangulatedToOrigFaceVertexIndexMap.clear(); + + size_t faceIndexOffset = 0; + + // For each polygon(face) + for (size_t i = 0; i < faceVertexCounts.size(); i++) { + uint32_t npolys = faceVertexCounts[i]; + + if (npolys < 3) { + err = fmt::format( + "faceVertex count must be 3(triangle) or " + "more(polygon), but got faceVertexCounts[{}] = {}\n", + i, npolys); + return false; + } + + if (faceIndexOffset + npolys > faceVertexIndices.size()) { + err = fmt::format( + "Invalid faceVertexIndices or faceVertexCounts. faceVertex index " + "exceeds faceVertexIndices.size() at [{}]\n", + i); + return false; + } + + if (npolys == 3) { + // No need for triangulation. + triangulatedFaceVertexCounts.push_back(3); + triangulatedFaceVertexIndices.push_back( + faceVertexIndices[faceIndexOffset + 0]); + triangulatedFaceVertexIndices.push_back( + faceVertexIndices[faceIndexOffset + 1]); + triangulatedFaceVertexIndices.push_back( + faceVertexIndices[faceIndexOffset + 2]); + triangulatedToOrigFaceVertexIndexMap.push_back(faceIndexOffset + 0); + triangulatedToOrigFaceVertexIndexMap.push_back(faceIndexOffset + 1); + triangulatedToOrigFaceVertexIndexMap.push_back(faceIndexOffset + 2); + triangulatedFaceCounts.push_back(1); +#if 1 + } else if (npolys == 4) { + // Use simple split + // TODO: Split at shortest edge for better triangulation. + triangulatedFaceVertexCounts.push_back(3); + triangulatedFaceVertexCounts.push_back(3); + + triangulatedFaceVertexIndices.push_back( + faceVertexIndices[faceIndexOffset + 0]); + triangulatedFaceVertexIndices.push_back( + faceVertexIndices[faceIndexOffset + 1]); + triangulatedFaceVertexIndices.push_back( + faceVertexIndices[faceIndexOffset + 2]); + + triangulatedFaceVertexIndices.push_back( + faceVertexIndices[faceIndexOffset + 0]); + triangulatedFaceVertexIndices.push_back( + faceVertexIndices[faceIndexOffset + 2]); + triangulatedFaceVertexIndices.push_back( + faceVertexIndices[faceIndexOffset + 3]); + + triangulatedToOrigFaceVertexIndexMap.push_back(faceIndexOffset + 0); + triangulatedToOrigFaceVertexIndexMap.push_back(faceIndexOffset + 1); + triangulatedToOrigFaceVertexIndexMap.push_back(faceIndexOffset + 2); + triangulatedToOrigFaceVertexIndexMap.push_back(faceIndexOffset + 0); + triangulatedToOrigFaceVertexIndexMap.push_back(faceIndexOffset + 2); + triangulatedToOrigFaceVertexIndexMap.push_back(faceIndexOffset + 3); + triangulatedFaceCounts.push_back(2); +#endif + } else { + // Find the normal axis of the polygon using Newell's method + T n = {BaseTy(0), BaseTy(0), BaseTy(0)}; + + size_t vi0; + size_t vi0_2; + + for (size_t k = 0; k < npolys; ++k) { + vi0 = faceVertexIndices[faceIndexOffset + k]; + + size_t j = (k + 1) % npolys; + vi0_2 = faceVertexIndices[faceIndexOffset + j]; + + if (vi0 >= points.size()) { + err = fmt::format("Invalid vertex index.\n"); + return false; + } + + if (vi0_2 >= points.size()) { + err = fmt::format("Invalid vertex index.\n"); + return false; + } + + T v0 = points[vi0]; + T v1 = points[vi0_2]; + + const T point1 = {v0[0], v0[1], v0[2]}; + const T point2 = {v1[0], v1[1], v1[2]}; + + T a = {point1[0] - point2[0], point1[1] - point2[1], + point1[2] - point2[2]}; + T b = {point1[0] + point2[0], point1[1] + point2[1], + point1[2] + point2[2]}; + + n[0] += (a[1] * b[2]); + n[1] += (a[2] * b[0]); + n[2] += (a[0] * b[1]); + } + BaseTy length_n = vlength(n); + // Check if zero length normal + if (std::fabs(length_n) < std::numeric_limits::epsilon()) { + err = "Degenerated polygon found.\n"; + return false; + } + + // Negative is to flip the normal to the correct direction + n = vnormalize(n); + + T axis_w, axis_v, axis_u; + axis_w = n; + T a; + if (std::fabs(axis_w[0]) > BaseTy(0.9999999)) { // TODO: use 1.0 - eps? + a = {BaseTy(0), BaseTy(1), BaseTy(0)}; + } else { + a = {BaseTy(1), BaseTy(0), BaseTy(0)}; + } + axis_v = vnormalize(vcross(axis_w, a)); + axis_u = vcross(axis_w, axis_v); + + using Point3D = std::array; + using Point2D = std::array; + std::vector polyline; + + // TMW change: Find best normal and project v0x and v0y to those + // coordinates, instead of picking a plane aligned with an axis (which + // can flip polygons). + + // Fill polygon data. + for (size_t k = 0; k < npolys; k++) { + size_t vidx = faceVertexIndices[faceIndexOffset + k]; + + value::float3 v = points[vidx]; + // Point3 polypoint = {v0[0],v0[1],v0[2]}; + + // world to local + Point3D loc = {vdot(v, axis_u), vdot(v, axis_v), vdot(v, axis_w)}; + + polyline.push_back({loc[0], loc[1]}); + } + + std::vector> polygon_2d; + // Single polygon only(no holes) + + std::vector indices = mapbox::earcut(polygon_2d); + // => result = 3 * faces, clockwise + + if ((indices.size() % 3) != 0) { + // This should not be happen, though. + err = "Failed to triangulate.\n"; + return false; + } + + size_t ntris = indices.size() / 3; + + // Up to 2GB tris. + if (ntris > (std::numeric_limits::max)()) { + err = "Too many triangles are generated.\n"; + return false; + } + + for (size_t k = 0; k < ntris; k++) { + triangulatedFaceVertexCounts.push_back(3); + triangulatedFaceVertexIndices.push_back( + faceVertexIndices[faceIndexOffset + indices[3 * k + 0]]); + triangulatedFaceVertexIndices.push_back( + faceVertexIndices[faceIndexOffset + indices[3 * k + 1]]); + triangulatedFaceVertexIndices.push_back( + faceVertexIndices[faceIndexOffset + indices[3 * k + 2]]); + + triangulatedToOrigFaceVertexIndexMap.push_back(faceIndexOffset + + indices[3 * k + 0]); + triangulatedToOrigFaceVertexIndexMap.push_back(faceIndexOffset + + indices[3 * k + 1]); + triangulatedToOrigFaceVertexIndexMap.push_back(faceIndexOffset + + indices[3 * k + 2]); + } + triangulatedFaceCounts.push_back(uint32_t(ntris)); + } + + faceIndexOffset += npolys; + } + + return true; +} +#endif + +#if 0 // not used atm. +// Building an Orthonormal Basis, Revisited +// http://jcgt.org/published/0006/01/01/ +static void GenerateBasis(const vec3 &n, vec3 *tangent, + vec3 *binormal) +{ + if (n[2] < 0.0f) { + const float a = 1.0f / (1.0f - n[2]); + const float b = n[0] * n[1] * a; + (*tangent) = vec3{1.0f - n[0] * n[0] * a, -b, n[0]}; + (*binormal) = vec3{b, n[1] * n[1] * a - 1.0f, -n[1]}; + } else { + const float a = 1.0f / (1.0f + n[2]); + const float b = -n[0] * n[1] * a; + (*tangent) = vec3{1.0f - n[0] * n[0] * a, b, -n[0]}; + (*binormal) = vec3{b, 1.0f - n[1] * n[1] * a, -n[1]}; + } +} +#endif + +struct ComputeTangentPackedVertexData { + // value::float3 position; + uint32_t point_index; + value::float3 normal; + value::float2 uv; + + // comparator for std::map + bool operator<(const DefaultPackedVertexData &rhs) const { + return memcmp(reinterpret_cast(this), + reinterpret_cast(&rhs), + sizeof(DefaultPackedVertexData)) > 0; + } +}; + +struct ComputeTangentPackedVertexDataHasher { + inline size_t operator()(const ComputeTangentPackedVertexData &v) const { + // Simple hasher using FNV1 32bit + // TODO: Use 64bit FNV1? + // TODO: Use spatial hash or LSH(LocallySensitiveHash) for position value. + static constexpr uint32_t kFNV_Prime = 0x01000193; + static constexpr uint32_t kFNV_Offset_Basis = 0x811c9dc5; + + const uint8_t *ptr = reinterpret_cast(&v); + size_t n = sizeof(DefaultPackedVertexData); + + uint32_t hash = kFNV_Offset_Basis; + for (size_t i = 0; i < n; i++) { + hash = (kFNV_Prime * hash) ^ (ptr[i]); + } + + return size_t(hash); + } +}; + +struct ComputeTangentPackedVertexDataEqual { + bool operator()(const ComputeTangentPackedVertexData &lhs, + const ComputeTangentPackedVertexData &rhs) const { + return memcmp(reinterpret_cast(&lhs), + reinterpret_cast(&rhs), + sizeof(ComputeTangentPackedVertexData)) == 0; + } +}; + +template +struct ComputeTangentVertexInput { + // std::vector positions; + std::vector point_indices; + std::vector normals; + std::vector uvs; + + size_t size() const { return point_indices.size(); } + + void get(size_t idx, PackedVert &output) const { + if (idx < point_indices.size()) { + output.point_index = point_indices[idx]; + } else { + output.point_index = ~0u; // never should reach here though. + } + if (idx < normals.size()) { + output.normal = normals[idx]; + } else { + output.normal = {0.0f, 0.0f, 0.0f}; + } + if (idx < uvs.size()) { + output.uv = uvs[idx]; + } else { + output.uv = {0.0f, 0.0f}; + } + } +}; + +template +struct ComputeTangentVertexOutput { + // std::vector positions; + std::vector point_indices; + std::vector normals; + std::vector uvs; + + size_t size() const { return point_indices.size(); } + + void push_back(const PackedVert &v) { + // positions.push_back(v.position); + point_indices.push_back(v.point_index); + normals.push_back(v.normal); + uvs.push_back(v.uv); + } +}; + +/// +/// Compute facevarying tangent and facevarying binormal. +/// +/// Reference: +/// http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-13-normal-mapping +/// +/// Implemented code uses two adjacent edge composed from three vertices v_{i}, +/// v_{i+1}, v_{i+2} for i < (N - 1) , where N is the number of vertices per +/// facet. +/// +/// This may produce unwanted tangent/binormal frame for ill-defined +/// polygon(quad, pentagon, ...). Also, we assume input mesh has well-formed and +/// has no or few vertices with similar property(position, uvs and normals) +/// +/// TODO: +/// - [ ] Implement better getSimilarVertexIndex in the above opengl-tutorial to +/// better average tangent/binormal. +/// - Use kNN search(e.g. nanoflann https://github.com/jlblancoc/nanoflann ), +/// or point-query by building BVH over the mesh points. +/// - BVH builder candidate: +/// - NanoRT https://github.com/lighttransport/nanort +/// - bvh https://github.com/madmann91/bvh +/// - Or we can quantize vertex attributes and compute locally sensitive +/// hashsing? https://dl.acm.org/doi/10.1145/3188745.3188846 +/// - [ ] Support robusut computing tangent/binormal on arbitrary mesh. +/// - e.g. vector field calculation, use instance-mesh algorithm, etc... +// - Use half-edges to find adjacent face/vertex. +/// +/// +/// @param[in] vertices Vertex points(`vertex` variability). +/// @param[in] faceVertexCounts faceVertexCounts of the mesh. +/// @param[in] faceVertexIndices faceVertexIndices of the mesh. +/// @param[in] texcoords Primary texcoords. +/// @param[in] normals normals. +/// @param[in] is_facevarying_input false = texcoords and normals are 'vertex' +/// variability. true = 'facevarying' variability. +/// @param[out] tangents Computed tangents; +/// @param[out] binormals Computed binormals; +/// @param[out] out_vertex_indices Vertex indices. +/// @param[out] err Error message. +/// +static bool ComputeTangentsAndBinormals( + const std::vector &vertices, + const std::vector &faceVertexCounts, + const std::vector &faceVertexIndices, + const std::vector &texcoords, const std::vector &normals, + bool is_facevarying_input, // false: 'vertex' varying + std::vector *tangents, std::vector *binormals, + std::vector *out_vertex_indices, std::string *err) { + if (!tangents) { + PUSH_ERROR_AND_RETURN("tangents arg is nullptr."); + } + + if (!binormals) { + PUSH_ERROR_AND_RETURN("binormals arg is nullptr."); + } + + if (!out_vertex_indices) { + PUSH_ERROR_AND_RETURN("out_indices arg is nullptr."); + } + + if (vertices.empty()) { + PUSH_ERROR_AND_RETURN("vertices is empty."); + } + + // At least 1 triangle face should exist. + if (faceVertexIndices.size() < 3) { + PUSH_ERROR_AND_RETURN("faceVertexIndices.size < 3"); + } + + if (texcoords.empty()) { + PUSH_ERROR_AND_RETURN("texcoords is empty"); + } + + if (normals.empty()) { + PUSH_ERROR_AND_RETURN("normals is empty"); + } + + if (is_facevarying_input) { + if (vertices.size() != faceVertexIndices.size()) { + PUSH_ERROR_AND_RETURN("Invalid vertices.size."); + } + if (texcoords.size() != faceVertexIndices.size()) { + PUSH_ERROR_AND_RETURN("Invalid texcoords.size."); + } + if (normals.size() != faceVertexIndices.size()) { + PUSH_ERROR_AND_RETURN("Invalid normals.size."); + } + } else { + uint32_t max_vert_index = + *std::max_element(faceVertexIndices.begin(), faceVertexIndices.end()); + if (max_vert_index >= vertices.size()) { + PUSH_ERROR_AND_RETURN("Invalid vertices.size."); + } + if (max_vert_index >= texcoords.size()) { + PUSH_ERROR_AND_RETURN("Invalid texcoords.size."); + } + if (max_vert_index >= normals.size()) { + PUSH_ERROR_AND_RETURN("Invalid normals.size."); + } + } + + bool hasFaceVertexCounts = true; + if (faceVertexCounts.size() == 0) { + // Assume all triangle faces. + if ((faceVertexIndices.size() % 3) != 0) { + PUSH_ERROR_AND_RETURN( + "Invalid faceVertexIndices. It must be all triangles: " + "faceVertexIndices.size % 3 == 0"); + } + hasFaceVertexCounts = false; + } + + // tn, bn = facevarying + std::vector tn(faceVertexIndices.size()); + memset(&tn.at(0), 0, sizeof(value::normal3f) * tn.size()); + std::vector bn(faceVertexIndices.size()); + memset(&bn.at(0), 0, sizeof(value::normal3f) * bn.size()); + + // + // 1. Compute facevarying tangent/binormal for each faceVertex. + // + size_t faceVertexIndexOffset{0}; + for (size_t i = 0; i < faceVertexCounts.size(); i++) { + size_t nv = hasFaceVertexCounts ? faceVertexCounts[i] : 3; + + if ((faceVertexIndexOffset + nv) >= faceVertexIndices.size()) { + // Invalid faceVertexIndices + PUSH_ERROR_AND_RETURN("Invalid value in faceVertexOffset."); + } + + if (nv < 3) { + PUSH_ERROR_AND_RETURN("Degenerated facet found."); + } + + // Process each two-edges per facet. + // + // Example: + // + // fv3 + // o----------------o fv2 + // \ / + // \ / + // o----------o + // fv0 fv1 + + // facet0: fv0, fv1, fv2 + // facet1: fv1, fv2, fv3 + + for (size_t f = 0; f < nv - 2; f++) { + size_t fid0 = faceVertexIndexOffset + f; + size_t fid1 = faceVertexIndexOffset + f + 1; + size_t fid2 = faceVertexIndexOffset + f + 2; + + uint32_t vf0 = + is_facevarying_input ? uint32_t(fid0) : faceVertexIndices[fid0]; + uint32_t vf1 = + is_facevarying_input ? uint32_t(fid1) : faceVertexIndices[fid1]; + uint32_t vf2 = + is_facevarying_input ? uint32_t(fid2) : faceVertexIndices[fid2]; + + if ((vf0 >= vertices.size()) || (vf1 >= vertices.size()) || + (vf2 >= vertices.size())) { + // index out-of-range + PUSH_ERROR_AND_RETURN( + "Invalid value in faceVertexIndices. some exceeds vertices.size()"); + } + + vec3 v1 = vertices[vf0]; + vec3 v2 = vertices[vf1]; + vec3 v3 = vertices[vf2]; + + float v1x = v1[0]; + float v1y = v1[1]; + float v1z = v1[2]; + + float v2x = v2[0]; + float v2y = v2[1]; + float v2z = v2[2]; + + float v3x = v3[0]; + float v3y = v3[1]; + float v3z = v3[2]; + + float w1x = 0.0f; + float w1y = 0.0f; + float w2x = 0.0f; + float w2y = 0.0f; + float w3x = 0.0f; + float w3y = 0.0f; + + if ((vf0 >= texcoords.size()) || (vf1 >= texcoords.size()) || + (vf2 >= texcoords.size())) { + // index out-of-range + PUSH_ERROR_AND_RETURN("Invalid index. some exceeds texcoords.size()"); + } + + { + vec2 uv1 = texcoords[vf0]; + vec2 uv2 = texcoords[vf1]; + vec2 uv3 = texcoords[vf2]; + + w1x = uv1[0]; + w1y = uv1[1]; + w2x = uv2[0]; + w2y = uv2[1]; + w3x = uv3[0]; + w3y = uv3[1]; + } + + float x1 = v2x - v1x; + float x2 = v3x - v1x; + float y1 = v2y - v1y; + float y2 = v3y - v1y; + float z1 = v2z - v1z; + float z2 = v3z - v1z; + + float s1 = w2x - w1x; + float s2 = w3x - w1x; + float t1 = w2y - w1y; + float t2 = w3y - w1y; + + float r = 1.0; + + if (std::fabs(double(s1 * t2 - s2 * t1)) > 1.0e-20) { + r /= (s1 * t2 - s2 * t1); + } + + vec3 tdir{(t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, + (t2 * z1 - t1 * z2) * r}; + vec3 bdir{(s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, + (s1 * z2 - s2 * z1) * r}; + + // + // NOTE: for quad or polygon mesh, this overwrites previous 2 facevarying + // points for each face. + // And this would not be a good way to compute tangents for + // quad/polygon. + // + + tn[fid0][0] = tdir[0]; + tn[fid0][1] = tdir[1]; + tn[fid0][2] = tdir[2]; + + tn[fid1][0] = tdir[0]; + tn[fid1][1] = tdir[1]; + tn[fid1][2] = tdir[2]; + + tn[fid2][0] = tdir[0]; + tn[fid2][1] = tdir[1]; + tn[fid2][2] = tdir[2]; + + bn[fid0][0] = bdir[0]; + bn[fid0][1] = bdir[1]; + bn[fid0][2] = bdir[2]; + + bn[fid1][0] = bdir[0]; + bn[fid1][1] = bdir[1]; + bn[fid1][2] = bdir[2]; + + bn[fid2][0] = bdir[0]; + bn[fid2][1] = bdir[1]; + bn[fid2][2] = bdir[2]; + } + + faceVertexIndexOffset += nv; + } + + // + // 2. Build indices(use same index for shared-vertex) + // + std::vector vertex_indices; // len = faceVertexIndices.size() + { + ComputeTangentVertexInput vertex_input; + ComputeTangentVertexOutput vertex_output; + + if (is_facevarying_input) { + // input position is still in 'vertex' variability. + for (size_t i = 0; i < faceVertexIndices.size(); i++) { + vertex_input.point_indices.push_back(faceVertexIndices[i]); + } + vertex_input.normals = normals; + vertex_input.uvs = texcoords; + } else { + // expand to facevarying. + for (size_t i = 0; i < faceVertexIndices.size(); i++) { + vertex_input.point_indices.push_back(faceVertexIndices[i]); + vertex_input.normals.push_back(normals[faceVertexIndices[i]]); + vertex_input.uvs.push_back(texcoords[faceVertexIndices[i]]); + } + } + + std::vector vertex_point_indices; + + BuildIndices, + ComputeTangentVertexOutput, + ComputeTangentPackedVertexData, + ComputeTangentPackedVertexDataHasher, + ComputeTangentPackedVertexDataEqual>( + vertex_input, vertex_output, vertex_indices, vertex_point_indices); + + DCOUT("faceVertexIndices.size : " << faceVertexIndices.size()); + DCOUT("# of indices after the build: " + << vertex_indices.size() << ", reduced " + << (faceVertexIndices.size() - vertex_indices.size()) << " indices."); + // We only need indices. Discard vertex_output and vertrex_point_indices + } + + const uint32_t num_verts = + *std::max_element(vertex_indices.begin(), vertex_indices.end()); + + // + // 3. normalize * orthogonalize; + // + + // per-vertex tangents/binormals + std::vector v_tn; + v_tn.assign(num_verts, {0.0f, 0.0f, 0.0f}); + + std::vector v_bn; + v_bn.assign(num_verts, {0.0f, 0.0f, 0.0f}); + + for (size_t i = 0; i < vertex_indices.size(); i++) { + value::normal3f Tn = tn[vertex_indices[i]]; + value::normal3f Bn = bn[vertex_indices[i]]; + + v_tn[vertex_indices[i]][0] += Tn[0]; + v_tn[vertex_indices[i]][1] += Tn[1]; + v_tn[vertex_indices[i]][2] += Tn[2]; + + v_bn[vertex_indices[i]][0] += Bn[0]; + v_bn[vertex_indices[i]][1] += Bn[1]; + v_bn[vertex_indices[i]][2] += Bn[2]; + } + + for (size_t i = 0; i < size_t(num_verts); i++) { + if (vlength(v_tn[i]) > 0.0f) { + v_tn[i] = vnormalize(v_tn[i]); + } + if (vlength(v_bn[i]) > 0.0f) { + v_bn[i] = vnormalize(v_bn[i]); + } + } + + tangents->assign(num_verts, {0.0f, 0.0f, 0.0f}); + binormals->assign(num_verts, {0.0f, 0.0f, 0.0f}); + + for (size_t i = 0; i < vertex_indices.size(); i++) { + value::normal3f n; + + // http://www.terathon.com/code/tangent.html + + n[0] = normals[vertex_indices[i]][0]; + n[1] = normals[vertex_indices[i]][1]; + n[2] = normals[vertex_indices[i]][2]; + + value::normal3f Tn = v_tn[vertex_indices[i]]; + value::normal3f Bn = v_bn[vertex_indices[i]]; + + // Gram-Schmidt orthogonalize + Tn = (Tn - n * vdot(n, Tn)); + if (vlength(Tn) > 0.0f) { + Tn = vnormalize(Tn); + } + + // Calculate handedness + if (vdot(vcross(n, Tn), Bn) < 0.0f) { + Tn = Tn * -1.0f; + } + + ((*tangents)[vertex_indices[i]])[0] = Tn[0]; + ((*tangents)[vertex_indices[i]])[1] = Tn[1]; + ((*tangents)[vertex_indices[i]])[2] = Tn[2]; + + ((*binormals)[vertex_indices[i]])[0] = Bn[0]; + ((*binormals)[vertex_indices[i]])[1] = Bn[1]; + ((*binormals)[vertex_indices[i]])[2] = Bn[2]; + } + + (*out_vertex_indices) = vertex_indices; + + return true; +} + +// +// Compute geometric normal in CCW(Counter Clock-Wise) manner +// Also computes the area of the input triangle. +// +inline static value::float3 GeometricNormal(const value::float3 v0, + const value::float3 v1, + const value::float3 v2, + float &area) { + const value::float3 v10 = v1 - v0; + const value::float3 v20 = v2 - v0; + + value::float3 Nf = vcross(v10, v20); // CCW + area = 0.5f * vlength(Nf); + Nf = vnormalize(Nf); + + return Nf; +} + +// +// Compute a normal for vertices. +// Normal vector is computed as weighted(by the area of the triangle) vector. +// +// TODO: Implement better normal calculation. ref. +// http://www.bytehazard.com/articles/vertnorm.html +// +static bool ComputeNormals(const std::vector &vertices, + const std::vector &faceVertexCounts, + const std::vector &faceVertexIndices, + std::vector &normals, std::string *err) { + normals.assign(vertices.size(), {0.0f, 0.0f, 0.0f}); + + size_t faceVertexIndexOffset{0}; + for (size_t f = 0; f < faceVertexCounts.size(); f++) { + size_t nv = faceVertexCounts[f]; + + if (nv < 3) { + PUSH_ERROR_AND_RETURN( + fmt::format("Invalid face num {} at faceVertexCounts[{}]", nv, f)); + } + + uint32_t vidx0 = faceVertexIndices[faceVertexIndexOffset + f + 0]; + uint32_t vidx1 = faceVertexIndices[faceVertexIndexOffset + f + 1]; + uint32_t vidx2 = faceVertexIndices[faceVertexIndexOffset + f + 2]; + + if ((vidx0 >= vertices.size()) || (vidx1 >= vertices.size()) || + (vidx2 >= vertices.size())) { + PUSH_ERROR_AND_RETURN( + fmt::format("vertexIndex exceeds vertices.size {}", vertices.size())); + } + + // For quad/polygon, first three vertices are used to compute face normal + // (Assume quad/polygon plane is co-planar) + float area{0.0f}; + value::float3 Nf = GeometricNormal(vertices[vidx0], vertices[vidx1], + vertices[vidx2], area); + + for (size_t v = 0; v < nv; v++) { + uint32_t vidx = faceVertexIndices[faceVertexIndexOffset + v]; + if (vidx >= vertices.size()) { + PUSH_ERROR_AND_RETURN(fmt::format( + "vertexIndex exceeds vertices.size {}", vertices.size())); + } + normals[vidx] += area * Nf; + } + + faceVertexIndexOffset += nv; + } + + for (size_t v = 0; v < normals.size(); v++) { + normals[v] = vnormalize(normals[v]); + } + + return true; +} + +} // namespace + +#if 0 +// Currently float2 only +std::vector ExtractPrimvarReadersFromMaterialNode( + const Prim &node) { + std::vector dst; + + if (!node.is()) { + return dst; + } + + for (const auto &child : node.children()) { + (void)child; + } + + // Traverse and find PrimvarReader_float2 shader. + return dst; +} + +nonstd::expected ConvertXform(const Stage &stage, + const Xform &xform) { + (void)stage; + + // TODO: timeSamples + + Node node; + if (auto m = xform.GetLocalMatrix()) { + node.local_matrix = m.value(); + } + + return std::move(node); +} +#endif + +namespace { + +bool ListUVNames(const RenderMaterial &material, + const std::vector &textures, + StringAndIdMap &si_map) { + // TODO: Use auto + auto fun_vec3 = [&](const ShaderParam ¶m) { + int32_t texId = param.textureId; + if ((texId >= 0) && (size_t(texId) < textures.size())) { + const UVTexture &tex = textures[size_t(texId)]; + if (tex.varname_uv.size()) { + if (!si_map.count(tex.varname_uv)) { + uint64_t slotId = si_map.size(); + DCOUT("Add textureSlot: " << tex.varname_uv << ", " << slotId); + si_map.add(tex.varname_uv, slotId); + } + } + } + }; + + auto fun_float = [&](const ShaderParam ¶m) { + int32_t texId = param.textureId; + if ((texId >= 0) && (size_t(texId) < textures.size())) { + const UVTexture &tex = textures[size_t(texId)]; + if (tex.varname_uv.size()) { + if (!si_map.count(tex.varname_uv)) { + uint64_t slotId = si_map.size(); + DCOUT("Add textureSlot: " << tex.varname_uv << ", " << slotId); + si_map.add(tex.varname_uv, slotId); + } + } + } + }; + + fun_vec3(material.surfaceShader.diffuseColor); + fun_vec3(material.surfaceShader.normal); + fun_float(material.surfaceShader.metallic); + fun_float(material.surfaceShader.roughness); + fun_float(material.surfaceShader.clearcoat); + fun_float(material.surfaceShader.clearcoatRoughness); + fun_float(material.surfaceShader.opacity); + fun_float(material.surfaceShader.opacityThreshold); + fun_float(material.surfaceShader.ior); + fun_float(material.surfaceShader.displacement); + fun_float(material.surfaceShader.occlusion); + + return true; +} + +#undef PushError + +} // namespace + +/// +/// Convert vertex variability either 'vertex' or 'facevarying' +/// +/// @param[in] to_vertex_varying true: Convert to 'vertrex' varying. false: +/// Convert to 'facevarying' +/// +bool RenderSceneConverter::ConvertVertexVariabilityImpl( + VertexAttribute &vattr, const bool to_vertex_varying, + const std::vector &faceVertexCounts, + const std::vector &faceVertexIndices) { + if (vattr.data.empty()) { + return true; + } + + if (vattr.variability == VertexVariability::Uniform) { + if (to_vertex_varying) { + auto result = UniformToVertex(vattr.get_data(), vattr.stride_bytes(), + faceVertexCounts, faceVertexIndices); + + if (!result) { + PUSH_ERROR_AND_RETURN( + fmt::format("Convert `{}` attribute with uniform-varying " + "to vertex-varying failed: {}", + vattr.name, result.error())); + } + + vattr.data = result.value(); + vattr.variability = VertexVariability::Vertex; + + } else { + auto result = UniformToFaceVarying(vattr.get_data(), vattr.stride_bytes(), + faceVertexCounts); + if (!result) { + PUSH_ERROR_AND_RETURN( + fmt::format("Convert uniform `{}` attribute to failed: {}", + vattr.name, result.error())); + } + + vattr.data = result.value(); + vattr.variability = VertexVariability::FaceVarying; + } + } else if (vattr.variability == VertexVariability::Constant) { + if (to_vertex_varying) { + auto result = ConstantToVertex(vattr.get_data(), vattr.stride_bytes(), + faceVertexCounts, faceVertexIndices); + + if (!result) { + PUSH_ERROR_AND_RETURN( + fmt::format("Convert `{}` attribute with uniform-varying " + "to vertex-varying failed: {}", + vattr.name, result.error())); + } + + vattr.data = result.value(); + vattr.variability = VertexVariability::Vertex; + + } else { + auto result = UniformToFaceVarying(vattr.get_data(), vattr.stride_bytes(), + faceVertexCounts); + if (!result) { + PUSH_ERROR_AND_RETURN( + fmt::format("Convert uniform `{}` attribute to failed: {}", + vattr.name, result.error())); + } + + vattr.data = result.value(); + vattr.variability = VertexVariability::FaceVarying; + } + + } else if ((vattr.variability == VertexVariability::Vertex) || + (vattr.variability == VertexVariability::Varying)) { + if (!to_vertex_varying) { + auto result = VertexToFaceVarying(vattr.get_data(), vattr.stride_bytes(), + faceVertexCounts, faceVertexIndices); + if (!result) { + PUSH_ERROR_AND_RETURN( + fmt::format("Convert vertex/varying `{}` attribute to failed: {}", + vattr.name, result.error())); + } + + vattr.data = result.value(); + vattr.variability = VertexVariability::FaceVarying; + } + + } else if (vattr.variability == VertexVariability::FaceVarying) { + if (to_vertex_varying) { + PUSH_ERROR_AND_RETURN( + "Internal error. `to_vertex_varying` should not be true when " + "FaceVarying."); + } + + } else { + PUSH_ERROR_AND_RETURN( + fmt::format("Unsupported/unimplemented interpolation: {} ", + to_string(vattr.variability))); + } + + return true; +} + +bool RenderSceneConverter::BuildVertexIndicesImpl(RenderMesh &mesh) { + // + // - If mesh is triangulated, use triangulatedFaceVertexIndices, otherwise use + // faceVertxIndices. + // - Make vertex attributes 'facevarying' variability + // - Assign same id for similar(currently identitical) vertex attribute. + // - Reorder vertex attributes to 'vertex' variability. + // + + const std::vector &fvIndices = + mesh.triangulatedFaceVertexIndices.size() + ? mesh.triangulatedFaceVertexIndices + : mesh.usdFaceVertexIndices; + + DefaultVertexInput vertex_input; + + size_t num_fvs = fvIndices.size(); + vertex_input.point_indices = fvIndices; + vertex_input.uv0s.assign(num_fvs, {0.0f, 0.0f}); + vertex_input.uv1s.assign(num_fvs, {0.0f, 0.0f}); + vertex_input.normals.assign(num_fvs, {0.0f, 0.0f, 0.0f}); + vertex_input.tangents.assign(num_fvs, {0.0f, 0.0f, 0.0f}); + vertex_input.binormals.assign(num_fvs, {0.0f, 0.0f, 0.0f}); + vertex_input.colors.assign(num_fvs, {0.0f, 0.0f, 0.0f}); + vertex_input.opacities.assign(num_fvs, 0.0f); + + if (mesh.normals.vertex_count()) { + if (!mesh.normals.is_facevarying()) { + PUSH_ERROR_AND_RETURN( + "Internal error. normals must be 'facevarying' variability."); + } + if (mesh.normals.vertex_count() != num_fvs) { + PUSH_ERROR_AND_RETURN( + "Internal error. The number of normal items does not match with " + "the number of facevarying items."); + } + } + + const value::float2 *texcoord0_ptr = nullptr; + const value::float2 *texcoord1_ptr = nullptr; + + for (const auto &it : mesh.texcoords) { + if (it.second.vertex_count() > 0) { + if (!it.second.is_facevarying()) { + PUSH_ERROR_AND_RETURN( + "Internal error. texcoords must be 'facevarying' variability."); + } + if (it.second.vertex_count() != num_fvs) { + PUSH_ERROR_AND_RETURN( + "Internal error. The number of texcoord items does not match " + "with the number of facevarying items."); + } + + if (it.first == 0) { + texcoord0_ptr = reinterpret_cast( + it.second.get_data().data()); + } else if (it.first == 1) { + texcoord1_ptr = reinterpret_cast( + it.second.get_data().data()); + } else { + // ignore. + } + } + } + + const value::float3 *tangents_ptr = nullptr; + const value::float3 *binormals_ptr = nullptr; + + if (texcoord0_ptr) { + if (mesh.tangents.vertex_count()) { + if (!mesh.tangents.is_facevarying()) { + PUSH_ERROR_AND_RETURN( + "Internal error. tangents must be 'facevarying' variability."); + } + if (mesh.tangents.vertex_count() != num_fvs) { + PUSH_ERROR_AND_RETURN( + "Internal error. The number of tangents items does not match " + "with the number of facevarying items."); + } + + tangents_ptr = reinterpret_cast( + mesh.tangents.get_data().data()); + } + + if (mesh.binormals.vertex_count()) { + if (!mesh.binormals.is_facevarying()) { + PUSH_ERROR_AND_RETURN( + "Internal error. binormals must be 'facevarying' variability."); + } + if (mesh.binormals.vertex_count() != num_fvs) { + PUSH_ERROR_AND_RETURN( + "Internal error. The number of binormals items does not match " + "with the number of facevarying items."); + } + binormals_ptr = reinterpret_cast( + mesh.binormals.get_data().data()); + } + } + + if (mesh.vertex_colors.vertex_count()) { + if (!mesh.vertex_colors.is_facevarying()) { + PUSH_ERROR_AND_RETURN( + "Internal error. vertex_colors must be 'facevarying' variability."); + } + if (mesh.vertex_colors.vertex_count() != num_fvs) { + PUSH_ERROR_AND_RETURN( + "Internal error. The number of vertex_color items does not match " + "with the number of facevarying items."); + } + } + + if (mesh.vertex_opacities.vertex_count()) { + if (!mesh.vertex_opacities.is_facevarying()) { + PUSH_ERROR_AND_RETURN( + "Internal error. vertex_opacities must be 'facevarying' " + "variability."); + } + if (mesh.vertex_colors.vertex_count() != num_fvs) { + PUSH_ERROR_AND_RETURN( + "Internal error. The number of vertex_opacity items does not match " + "with the number of facevarying items."); + } + } + + const value::float3 *normals_ptr = + (mesh.normals.vertex_count() > 0) + ? reinterpret_cast( + mesh.normals.get_data().data()) + : nullptr; + const value::float3 *colors_ptr = + (mesh.vertex_colors.vertex_count() > 0) + ? reinterpret_cast( + mesh.vertex_colors.get_data().data()) + : nullptr; + const float *opacities_ptr = + (mesh.vertex_opacities.vertex_count() > 0) + ? reinterpret_cast( + mesh.vertex_opacities.get_data().data()) + : nullptr; + + for (size_t i = 0; i < num_fvs; i++) { + size_t fvi = fvIndices[i]; + if (fvi >= num_fvs) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Invalid faceVertexIndex {}. Must be less than {}", fvi, num_fvs)); + } + + if (normals_ptr) { + vertex_input.normals[i] = normals_ptr[i]; + } + if (texcoord0_ptr) { + vertex_input.uv0s[i] = texcoord0_ptr[i]; + } + if (texcoord1_ptr) { + vertex_input.uv1s[i] = texcoord1_ptr[i]; + } + if (tangents_ptr) { + vertex_input.tangents[i] = tangents_ptr[i]; + } + if (binormals_ptr) { + vertex_input.binormals[i] = binormals_ptr[i]; + } + if (colors_ptr) { + vertex_input.colors[i] = colors_ptr[i]; + } + if (opacities_ptr) { + vertex_input.opacities[i] = opacities_ptr[i]; + } + } + + std::vector out_indices; + std::vector out_point_indices; // to reorder position data + DefaultVertexOutput vertex_output; + + BuildIndices, + DefaultVertexOutput, + DefaultPackedVertexData, DefaultPackedVertexDataHasher, + DefaultPackedVertexDataEqual>(vertex_input, vertex_output, + out_indices, out_point_indices); + + if (out_indices.size() != out_point_indices.size()) { + PUSH_ERROR_AND_RETURN( + "Internal error. out_indices.size != out_point_indices."); + } + + DCOUT("faceVertexIndices.size : " << fvIndices.size()); + DCOUT("# of indices after the build: " + << out_indices.size() << ", reduced " + << (fvIndices.size() - out_indices.size()) << " indices."); + + if (mesh.is_triangulated()) { + mesh.triangulatedFaceVertexIndices = out_indices; + } else { + mesh.usdFaceVertexIndices = out_indices; + } + + // + // Reorder 'vertex' varying attributes(points, jointIndices/jointWeights, + // BlendShape points, ...) + // TODO: Preserve input order as much as possible. + // + { + uint32_t numPoints = + *std::max_element(out_indices.begin(), out_indices.end()) + 1; + { + std::vector tmp_points(numPoints); + for (size_t i = 0; i < out_point_indices.size(); i++) { + if (out_point_indices[i] >= mesh.points.size()) { + PUSH_ERROR_AND_RETURN("Internal error. point index out-of-range."); + } + tmp_points[out_indices[i]] = mesh.points[out_point_indices[i]]; + } + mesh.points.swap(tmp_points); + } + + if (mesh.joint_and_weights.jointIndices.size()) { + if (mesh.joint_and_weights.elementSize < 1) { + PUSH_ERROR_AND_RETURN( + "Internal error. Invalid elementSize in mesh.joint_and_weights."); + } + uint32_t elementSize = uint32_t(mesh.joint_and_weights.elementSize); + std::vector tmp_indices(numPoints * elementSize); + std::vector tmp_weights(numPoints * elementSize); + for (size_t i = 0; i < out_point_indices.size(); i++) { + if ((elementSize * out_point_indices[i]) >= + mesh.joint_and_weights.jointIndices.size()) { + PUSH_ERROR_AND_RETURN( + "Internal error. point index exceeds jointIndices.size."); + } + for (size_t k = 0; k < elementSize; k++) { + tmp_indices[elementSize * out_indices[i] + k] = + mesh.joint_and_weights + .jointIndices[elementSize * out_point_indices[i] + k]; + } + + if ((elementSize * out_point_indices[i]) >= + mesh.joint_and_weights.jointWeights.size()) { + PUSH_ERROR_AND_RETURN( + "Internal error. point index exceeds jointWeights.size."); + } + + for (size_t k = 0; k < elementSize; k++) { + tmp_weights[elementSize * out_indices[i] + k] = + mesh.joint_and_weights + .jointWeights[elementSize * out_point_indices[i] + k]; + } + } + mesh.joint_and_weights.jointIndices.swap(tmp_indices); + mesh.joint_and_weights.jointWeights.swap(tmp_weights); + } + + // TODO: Reorder BlendShape points + } + + // Other 'facevarying' attributes are now 'vertex' variability + if (normals_ptr) { + mesh.normals.set_buffer( + reinterpret_cast(vertex_output.normals.data()), + vertex_output.normals.size() * sizeof(value::float3)); + mesh.normals.variability = VertexVariability::Vertex; + } + + if (texcoord0_ptr) { + mesh.texcoords[0].set_buffer( + reinterpret_cast(vertex_output.uv0s.data()), + vertex_output.uv0s.size() * sizeof(value::float2)); + mesh.texcoords[0].variability = VertexVariability::Vertex; + } + + if (texcoord1_ptr) { + mesh.texcoords[1].set_buffer( + reinterpret_cast(vertex_output.uv1s.data()), + vertex_output.uv1s.size() * sizeof(value::float2)); + mesh.texcoords[1].variability = VertexVariability::Vertex; + } + + if (tangents_ptr) { + mesh.tangents.set_buffer( + reinterpret_cast(vertex_output.tangents.data()), + vertex_output.tangents.size() * sizeof(value::float3)); + mesh.tangents.variability = VertexVariability::Vertex; + } + + if (binormals_ptr) { + mesh.binormals.set_buffer( + reinterpret_cast(vertex_output.binormals.data()), + vertex_output.binormals.size() * sizeof(value::float3)); + mesh.binormals.variability = VertexVariability::Vertex; + } + + if (colors_ptr) { + mesh.vertex_colors.set_buffer( + reinterpret_cast(vertex_output.colors.data()), + vertex_output.colors.size() * sizeof(value::float3)); + mesh.vertex_colors.variability = VertexVariability::Vertex; + } + + if (opacities_ptr) { + mesh.vertex_opacities.set_buffer( + reinterpret_cast(vertex_output.opacities.data()), + vertex_output.opacities.size() * sizeof(float)); + mesh.vertex_opacities.variability = VertexVariability::Vertex; + } + + return true; +} + +bool RenderSceneConverter::ConvertMesh( + const RenderSceneConverterEnv &env, const Path &abs_path, + const GeomMesh &mesh, const MaterialPath &material_path, + const std::map &subset_material_path_map, + // const std::map &rmaterial_idMap, + const StringAndIdMap &rmaterial_map, + const std::vector &material_subsets, + const std::vector> + &blendshapes, + RenderMesh *dstMesh) { + // + // Steps: + // + // 1. Get points, faceVertexIndices and faceVertexOffsets at specified time. + // - Validate GeomSubsets + // 2. Assign Material and list up texcoord primvars + // 3. convert texcoord, normals, vetexcolor(displaycolors) + // - First try to convert it to `vertex` varying(Can be drawn with single + // index buffer) + // - Otherwise convert to `facevarying` as the last resort. + // 4. Triangulate indices when `triangulate` is enabled. + // - Triangulate texcoord, normals, vertexcolor. + // 5. Convert Skin weights + // 6. Convert BlendShape + // 7. Build indices(convert 'facevarying' to 'vertrex') + // 8. Calcualte normals(if not present in the mesh) + // 9. Build tangent frame(for normal mapping) + // + // + + if (!dstMesh) { + PUSH_ERROR_AND_RETURN("`dst` mesh pointer is nullptr"); + } + + RenderMesh dst; + + dst.is_rightHanded = + (mesh.orientation.get_value() == tinyusdz::Orientation::RightHanded); + dst.doubleSided = mesh.doubleSided.get_value(); + + // + // 1. Mandatory attribute: points, faceVertexCounts and faceVertexIndices. + // + // TODO: Make error when Mesh's indices is empty? + // + + { + std::vector points; + bool ret = EvaluateTypedAnimatableAttribute( + env.stage, mesh.points, "points", &points, &_err, env.timecode, + value::TimeSampleInterpolationType::Linear); + if (!ret) { + return false; + } + + if (points.empty()) { + PUSH_ERROR_AND_RETURN( + fmt::format("`points` is empty. Prim {}", abs_path)); + } + + dst.points.resize(points.size()); + memcpy(dst.points.data(), points.data(), + sizeof(value::float3) * points.size()); + } + + { + std::vector indices; + bool ret = EvaluateTypedAnimatableAttribute( + env.stage, mesh.faceVertexIndices, "faceVertexIndices", &indices, &_err, + env.timecode, value::TimeSampleInterpolationType::Held); + if (!ret) { + return false; + } + + for (size_t i = 0; i < indices.size(); i++) { + if (indices[i] < 0) { + PUSH_ERROR_AND_RETURN(fmt::format( + "faceVertexIndices[{}] contains negative index value {}.", i, + indices[i])); + } + if (size_t(indices[i]) > dst.points.size()) { + PUSH_ERROR_AND_RETURN( + fmt::format("faceVertexIndices[{}] {} exceeds points.size {}.", i, + indices[i], dst.points.size())); + } + dst.usdFaceVertexIndices.push_back(uint32_t(indices[i])); + } + } + + { + std::vector counts; + bool ret = EvaluateTypedAnimatableAttribute( + env.stage, mesh.faceVertexCounts, "faceVertexCounts", &counts, &_err, + env.timecode, value::TimeSampleInterpolationType::Held); + if (!ret) { + return false; + } + + size_t sumCounts = 0; + dst.usdFaceVertexCounts.clear(); + for (size_t i = 0; i < counts.size(); i++) { + if (counts[i] < 3) { + PUSH_ERROR_AND_RETURN( + fmt::format("faceVertexCounts[{}] contains invalid value {}. The " + "count value must be >= 3", + i, counts[i])); + } + + if ((sumCounts + size_t(counts[i])) > dst.usdFaceVertexIndices.size()) { + PUSH_ERROR_AND_RETURN(fmt::format( + "faceVertexCounts[{}] exceeds faceVertexIndices.size {}.", i, + dst.usdFaceVertexIndices.size())); + } + dst.usdFaceVertexCounts.push_back(uint32_t(counts[i])); + sumCounts += size_t(counts[i]); + } + } + + // + // 2. bindMaterial GeoMesh and GeomSubset. + // + // Assume Material conversion is done before ConvertMesh. + // Here we only assign RenderMaterial id and extract GeomSubset::indices + // information. + // + + DCOUT("rmaterial_ap.size " << rmaterial_map.size()); + if (rmaterial_map.count(material_path.material_path)) { + dst.material_id = int(rmaterial_map.at(material_path.material_path)); + } + + if (rmaterial_map.count(material_path.backface_material_path)) { + dst.backface_material_id = + int(rmaterial_map.at(material_path.backface_material_path)); + } + + if (env.mesh_config.validate_geomsubset) { + size_t elementCount = dst.usdFaceVertexCounts.size(); + + if (material_subsets.size() && + mesh.subsetFamilyTypeMap.count(value::token("materialBind"))) { + const GeomSubset::FamilyType familyType = + mesh.subsetFamilyTypeMap.at(value::token("materialBind")); + if (!GeomSubset::ValidateSubsets(material_subsets, elementCount, + familyType, &_err)) { + PUSH_ERROR_AND_RETURN("GeomSubset validation failed."); + } + } + } + + for (const auto &psubset : material_subsets) { + MaterialSubset ms; + ms.prim_name = psubset->name; + // ms.prim_index = // TODO + ms.abs_path = abs_path.prim_part() + std::string("/") + psubset->name; + ms.display_name = psubset->meta.displayName.value_or(""); + + // TODO: Raise error when indices is empty? + if (psubset->indices.authored()) { + std::vector indices; // index to faceVertexCounts + bool ret = EvaluateTypedAnimatableAttribute( + env.stage, psubset->indices, "indices", &indices, &_err, env.timecode, + value::TimeSampleInterpolationType::Held); + if (!ret) { + return false; + } + + ms.usdIndices = indices; + } + + if (subset_material_path_map.count(psubset->name)) { + const auto &mp = subset_material_path_map.at(psubset->name); + if (rmaterial_map.count(mp.material_path)) { + ms.material_id = int(rmaterial_map.at(mp.material_path)); + DCOUT("MaterialSubset " << psubset->name << " : material_id " + << ms.material_id); + } + if (rmaterial_map.count(mp.backface_material_path)) { + ms.backface_material_id = + int(rmaterial_map.at(mp.backface_material_path)); + DCOUT("MaterialSubset " << psubset->name << " : backface_material_id " + << ms.backface_material_id); + } + } + + // TODO: Ensure prim_name is unique. + dst.material_subsetMap[ms.prim_name] = ms; + } + + // + // List up texcoords in this mesh. + // - If no material assigned to this mesh, look into + // `default_texcoords_primvar_name` + // - If materials are assigned, find all corresponding UV primvars in this + // mesh. + // + + // key:slotId, value:texcoord data + std::unordered_map uvAttrs; + + // We need Material info to get corresponding primvar name. + if (rmaterial_map.empty()) { + // No material assigned to the Mesh, but we may still want texcoords solely( + // assign material after the conversion) + // So find a primvar whose name matches default texcoord name. + if (mesh.has_primvar(env.mesh_config.default_texcoords_primvar_name)) { + DCOUT("uv primvar with default_texcoords_primvar_name found."); + auto ret = GetTextureCoordinate( + env.stage, mesh, env.mesh_config.default_texcoords_primvar_name, + env.timecode, env.tinterp); + if (ret) { + const VertexAttribute vattr = ret.value(); + + // Use slotId 0 + uvAttrs[0] = vattr; + } else { + PUSH_WARN("Failed to get texture coordinate for `" + << env.mesh_config.default_texcoords_primvar_name + << "` : " << ret.error()); + } + } + } else { + for (auto mit = rmaterial_map.i_begin(); mit != rmaterial_map.i_end(); + mit++) { + int64_t rmaterial_id = int64_t(mit->first); + + if ((rmaterial_id > -1) && (size_t(rmaterial_id) < materials.size())) { + const RenderMaterial &material = materials[size_t(rmaterial_id)]; + + StringAndIdMap uvname_map; + if (!ListUVNames(material, textures, uvname_map)) { + DCOUT("Failed to list UV names"); + return false; + } + + for (auto it = uvname_map.i_begin(); it != uvname_map.i_end(); it++) { + uint64_t slotId = it->first; + std::string uvname = it->second; + + if (!uvAttrs.count(uint32_t(slotId))) { + auto ret = GetTextureCoordinate(env.stage, mesh, uvname, + env.timecode, env.tinterp); + if (ret) { + const VertexAttribute vattr = ret.value(); + + uvAttrs[uint32_t(slotId)] = vattr; + } else { + PUSH_WARN("Failed to get texture coordinate for `" + << uvname << "` : " << ret.error()); + } + } + } + } + } + } + + uint32_t num_vertices = uint32_t(dst.points.size()); + uint32_t num_faces = uint32_t(dst.usdFaceVertexCounts.size()); + uint32_t num_face_vertex_indices = uint32_t(dst.usdFaceVertexIndices.size()); + + if (mesh.has_primvar(env.mesh_config.default_tangents_primvar_name)) { + GeomPrimvar pvar; + + if (!GetGeomPrimvar(env.stage, &mesh, + env.mesh_config.default_tangents_primvar_name, &pvar, + &_err)) { + return false; + } + + if (!ToVertexAttribute(pvar, env.mesh_config.default_tangents_primvar_name, + num_vertices, num_faces, num_face_vertex_indices, + dst.tangents, &_err, env.timecode, env.tinterp)) { + return false; + } + } + + if (mesh.has_primvar(env.mesh_config.default_binormals_primvar_name)) { + GeomPrimvar pvar; + + if (!GetGeomPrimvar(env.stage, &mesh, + env.mesh_config.default_binormals_primvar_name, &pvar, + &_err)) { + return false; + } + + if (!ToVertexAttribute(pvar, env.mesh_config.default_binormals_primvar_name, + num_vertices, num_faces, num_face_vertex_indices, + dst.binormals, &_err, env.timecode, env.tinterp)) { + return false; + } + } + + constexpr auto kDisplayColor = "displayColor"; + if (mesh.has_primvar(kDisplayColor)) { + GeomPrimvar pvar; + + if (!GetGeomPrimvar(env.stage, &mesh, kDisplayColor, &pvar, &_err)) { + return false; + } + + VertexAttribute vcolor; + if (!ToVertexAttribute(pvar, kDisplayColor, num_vertices, num_faces, + num_face_vertex_indices, vcolor, &_err, env.timecode, + env.tinterp)) { + return false; + } + + if ((vcolor.elementSize == 1) && (vcolor.vertex_count() == 1) && + (vcolor.stride_bytes() == 3 * 4)) { + memcpy(&dst.displayColor, vcolor.data.data(), vcolor.stride_bytes()); + } else { + dst.vertex_colors = vcolor; + } + } + + constexpr auto kDisplayOpacity = "displayOpacity"; + if (mesh.has_primvar(kDisplayOpacity)) { + GeomPrimvar pvar; + if (!GetGeomPrimvar(env.stage, &mesh, kDisplayOpacity, &pvar, &_err)) { + return false; + } + + VertexAttribute vopacity; + if (!ToVertexAttribute(pvar, kDisplayOpacity, num_vertices, num_faces, + num_face_vertex_indices, vopacity, &_err, + env.timecode, env.tinterp)) { + return false; + } + + if ((vopacity.elementSize == 1) && (vopacity.vertex_count() == 1) && + (vopacity.stride_bytes() == 4)) { + memcpy(&dst.displayOpacity, vopacity.data.data(), + vopacity.stride_bytes()); + } else { + dst.vertex_opacities = vopacity; + } + } + + // + // Check if the Mesh can be drawn with single index buffer during converting + // normals/texcoords/displayColors/displayOpacities, since OpenGL and Vulkan + // does not support drawing a primitive with multiple index buffers. + // + // If the Mesh contains any face-varying attribute, + // First try to convert it 'vertex' variabily, if it fails, all attribute are + // converted to face-varying so that the Mesh can be drawn without index + // buffer. This will hurt the performance of rendering in OpenGL/Vulkan, + // especially when the Mesh is animated with skinning. + // + // We leave user-defined primvar as-is, so no check for it. + // + bool is_single_indexable{true}; + + // + // Convert normals + // + { + Interpolation interp = mesh.get_normalsInterpolation(); + std::vector normals; + + if (mesh.has_primvar("normals")) { // primvars:normals + GeomPrimvar pvar; + if (!GetGeomPrimvar(env.stage, &mesh, "normals", &pvar, &_err)) { + return false; + } + + if (!pvar.flatten_with_indices(env.timecode, &normals, env.tinterp, + &_err)) { + PUSH_ERROR_AND_RETURN("Failed to expand `normals` primvar."); + } + + } else if (mesh.normals.authored()) { // look 'normals' + if (!EvaluateTypedAnimatableAttribute(env.stage, mesh.normals, "normals", + &normals, &_err, env.timecode, + env.tinterp)) { + } + } + + dst.normals.get_data().resize(normals.size() * sizeof(value::normal3f)); + memcpy(dst.normals.get_data().data(), normals.data(), + normals.size() * sizeof(value::normal3f)); + dst.normals.elementSize = 1; + dst.normals.stride = sizeof(value::normal3f); + dst.normals.format = VertexAttributeFormat::Vec3; + + if (interp == Interpolation::Varying) { + dst.normals.variability = VertexVariability::Varying; + } else if (interp == Interpolation::Constant) { + dst.normals.variability = VertexVariability::Constant; + } else if (interp == Interpolation::Uniform) { + dst.normals.variability = VertexVariability::Uniform; + } else if (interp == Interpolation::Vertex) { + dst.normals.variability = VertexVariability::Vertex; + } else if (interp == Interpolation::FaceVarying) { + dst.normals.variability = VertexVariability::FaceVarying; + } else { + PUSH_ERROR_AND_RETURN( + "[Internal Error] Invalid interpolation value for normals."); + } + dst.normals.indices.clear(); + dst.normals.name = "normals"; + + if (is_single_indexable && + (dst.normals.variability == VertexVariability::FaceVarying)) { + VertexAttribute va_normals; + if (TryConvertFacevaryingToVertex( + dst.normals, &va_normals, dst.usdFaceVertexIndices, &_warn, + env.mesh_config.facevarying_to_vertex_eps)) { + DCOUT("normals is converted to 'vertex' varying."); + dst.normals = std::move(va_normals); + } else { + DCOUT( + "normals cannot be converted to 'vertex' varying. Staying " + "'facevarying'"); + DCOUT("warn = " << _warn); + is_single_indexable = false; + } + } + } + + // + // Convert UVs + // + + for (const auto &it : uvAttrs) { + uint64_t slotId = it.first; + const VertexAttribute &vattr = it.second; + + if (vattr.format != VertexAttributeFormat::Vec2) { + PUSH_ERROR_AND_RETURN( + fmt::format("Texcoord VertexAttribute must be Vec2 type.\n")); + } + + if (vattr.element_size() != 1) { + PUSH_ERROR_AND_RETURN( + fmt::format("elementSize must be 1 for Texcoord attribute.")); + } + + DCOUT("Add texcoord attr `" << vattr.name << "` to slot Id " << slotId); + + if (is_single_indexable && + (vattr.variability == VertexVariability::FaceVarying)) { + VertexAttribute va_uvs; + if (TryConvertFacevaryingToVertex( + vattr, &va_uvs, dst.usdFaceVertexIndices, &_warn, + env.mesh_config.facevarying_to_vertex_eps)) { + DCOUT("texcoord[" << slotId << "] is converted to 'vertex' varying."); + dst.texcoords[uint32_t(slotId)] = va_uvs; + } else { + DCOUT("texcoord[" << slotId + << "] cannot be converted to 'vertex' varying. " + "Staying 'facevarying'"); + is_single_indexable = false; + dst.texcoords[uint32_t(slotId)] = vattr; + } + } else { + dst.texcoords[uint32_t(slotId)] = vattr; + } + } + + if (dst.vertex_colors.vertex_count() > 1) { + VertexAttribute vattr = dst.vertex_colors; // copy + + if (vattr.format != VertexAttributeFormat::Vec3) { + PUSH_ERROR_AND_RETURN( + fmt::format("Color VertexAttribute must be Vec3 type.\n")); + } + + if (vattr.element_size() != 1) { + PUSH_ERROR_AND_RETURN( + fmt::format("elementSize = 1 expected for VertexColor, but got {}", + vattr.element_size())); + } + + if (is_single_indexable && + (dst.vertex_colors.variability == VertexVariability::FaceVarying)) { + VertexAttribute va; + if (TryConvertFacevaryingToVertex( + dst.vertex_colors, &va, dst.usdFaceVertexIndices, &_warn, + env.mesh_config.facevarying_to_vertex_eps)) { + dst.vertex_colors = std::move(va); + } else { + DCOUT( + "vertex_colors cannot be converted to 'vertex' varying. Staying " + "'facevarying'"); + is_single_indexable = false; + } + } + } + + if (dst.vertex_opacities.vertex_count() > 1) { + VertexAttribute vattr = dst.vertex_opacities; // copy + + if (vattr.format != VertexAttributeFormat::Float) { + PUSH_ERROR_AND_RETURN( + fmt::format("Opacity VertexAttribute must be Float type.\n")); + } + + if (vattr.element_size() != 1) { + PUSH_ERROR_AND_RETURN( + fmt::format("elementSize = 1 expected for VertexOpacity, but got {}", + vattr.element_size())); + } + + if (is_single_indexable && + (dst.vertex_opacities.variability == VertexVariability::FaceVarying)) { + VertexAttribute va; + if (TryConvertFacevaryingToVertex( + dst.vertex_opacities, &va, dst.usdFaceVertexIndices, &_warn, + env.mesh_config.facevarying_to_vertex_eps)) { + dst.vertex_opacities = std::move(va); + } else { + DCOUT( + "vertex_opacities cannot be converted to 'vertex' varying. Staying " + "'facevarying'"); + is_single_indexable = false; + } + } + } + + DCOUT(mesh.name << " : is_single_indexable = " << is_single_indexable); + + // + // Convert built-in vertex attributes to either 'vertex' or 'facevarying' + // + { + if (!ConvertVertexVariabilityImpl(dst.normals, is_single_indexable, + dst.usdFaceVertexCounts, + dst.usdFaceVertexIndices)) { + return false; + } + for (auto &it : dst.texcoords) { + if (!ConvertVertexVariabilityImpl(it.second, is_single_indexable, + dst.usdFaceVertexCounts, + dst.usdFaceVertexIndices)) { + return false; + } + } + + if (!ConvertVertexVariabilityImpl(dst.vertex_colors, is_single_indexable, + dst.usdFaceVertexCounts, + dst.usdFaceVertexIndices)) { + return false; + } + if (!ConvertVertexVariabilityImpl(dst.vertex_opacities, is_single_indexable, + dst.usdFaceVertexCounts, + dst.usdFaceVertexIndices)) { + return false; + } + } + + /// + /// 4. Triangulate + /// - triangulate faceVertexCounts, faceVertexIndices + /// - Remap faceIndex in MaterialSubset(GeomSubset). + /// - Triangulate vertex attributes(normals, uvcoords, vertex + /// colors/opacities). + /// + bool triangulate = env.mesh_config.triangulate; + if (triangulate) { + DCOUT("Triangulate mesh"); + std::vector triangulatedFaceVertexCounts; // should be all 3's + std::vector triangulatedFaceVertexIndices; + std::vector + triangulatedToOrigFaceVertexIndexMap; // used for rearrange facevertex + // attrib + std::vector + triangulatedFaceCounts; // used for rearrange face indices(e.g + // GeomSubset indices) + + std::string err; + + if (!TriangulatePolygon( + dst.points, dst.usdFaceVertexCounts, dst.usdFaceVertexIndices, + triangulatedFaceVertexCounts, triangulatedFaceVertexIndices, + triangulatedToOrigFaceVertexIndexMap, triangulatedFaceCounts, + err)) { + PUSH_ERROR_AND_RETURN("Triangulation failed: " + err); + } + + if (dst.material_subsetMap.size()) { + // Remap faceId in GeomSubsets + + // + // size: len(triangulatedFaceCounts) + // value: array index in triangulatedFaceVertexCounts + // Up to 4GB faces. + // + std::vector faceIndexOffsets; + faceIndexOffsets.resize(triangulatedFaceCounts.size()); + + size_t faceIndexOffset = 0; + for (size_t i = 0; i < triangulatedFaceCounts.size(); i++) { + size_t ncount = triangulatedFaceCounts[i]; + + faceIndexOffsets[i] = uint32_t(faceIndexOffset); + // DCOUT("faceIndexOffset[" << i << "] = " << faceIndexOffsets[i]); + + faceIndexOffset += ncount; + + if (faceIndexOffset >= std::numeric_limits::max()) { + PUSH_ERROR_AND_RETURN("Triangulated Mesh contains 4G or more faces."); + } + } + + // Remap indices in MaterialSubset + // + // example: + // + // faceVertexCounts = [4, 4] + // faceVertexIndices = [0, 1, 2, 3, 4, 5, 6, 7] + // + // triangulatedFaceVertexCounts = [3, 3, 3, 3] + // triangulatedFaceVertexIndices = [0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7] + // triangulatedFaceCounts = [2, 2] + // + // geomsubset.indices = [0, 1] # index to faceVertexCounts + // faceIndexOffsets = [0, 2] + // + // => triangulated geomsubset.indices = [0, 1, 2, 3] # index to + // triangulatedFaceVertexCounts + // + // + for (auto &it : dst.material_subsetMap) { + std::vector triangulated_indices; + + for (size_t i = 0; i < it.second.usdIndices.size(); i++) { + int32_t srcIndex = it.second.usdIndices[i]; + if (srcIndex < 0) { + PUSH_ERROR_AND_RETURN("Invalid index value in GeomSubset."); + } + + uint32_t baseFaceIndex = faceIndexOffsets[size_t(srcIndex)]; + // DCOUT(i << ", baseFaceIndex = " << baseFaceIndex); + + for (size_t k = 0; k < triangulatedFaceCounts[uint32_t(srcIndex)]; + k++) { + if ((baseFaceIndex + k) > (std::numeric_limits::max)()) { + PUSH_ERROR_AND_RETURN(fmt::format("Index value exceeds 2GB.")); + } + // assume triangulated faceIndex in each polygon is monotonically + // increasing. + triangulated_indices.push_back(int(baseFaceIndex + k)); + } + } + + it.second.triangulatedIndices = std::move(triangulated_indices); + } + } + + // + // Triangulate built-in vertex attributes. + // + { + if (!TriangulateVertexAttribute(dst.normals, dst.usdFaceVertexCounts, + triangulatedToOrigFaceVertexIndexMap, + triangulatedFaceCounts, + triangulatedFaceVertexIndices, &_err)) { + PUSH_ERROR_AND_RETURN("Failed to triangulate normals attribute."); + } + + if (!TriangulateVertexAttribute(dst.tangents, dst.usdFaceVertexCounts, + triangulatedToOrigFaceVertexIndexMap, + triangulatedFaceCounts, + triangulatedFaceVertexIndices, &_err)) { + PUSH_ERROR_AND_RETURN("Failed to triangulate tangents attribute."); + } + + if (!TriangulateVertexAttribute(dst.binormals, dst.usdFaceVertexCounts, + triangulatedToOrigFaceVertexIndexMap, + triangulatedFaceCounts, + triangulatedFaceVertexIndices, &_err)) { + PUSH_ERROR_AND_RETURN("Failed to triangulate binormals attribute."); + } + + for (auto &it : dst.texcoords) { + if (!TriangulateVertexAttribute(it.second, dst.usdFaceVertexCounts, + triangulatedToOrigFaceVertexIndexMap, + triangulatedFaceCounts, + triangulatedFaceVertexIndices, &_err)) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Failed to triangulate texcoords[{}] attribute.", it.first)); + } + } + + if (!TriangulateVertexAttribute( + dst.vertex_colors, dst.usdFaceVertexCounts, + triangulatedToOrigFaceVertexIndexMap, triangulatedFaceCounts, + triangulatedFaceVertexIndices, &_err)) { + PUSH_ERROR_AND_RETURN("Failed to triangulate vertex_colors attribute."); + } + + if (!TriangulateVertexAttribute( + dst.vertex_opacities, dst.usdFaceVertexCounts, + triangulatedToOrigFaceVertexIndexMap, triangulatedFaceCounts, + triangulatedFaceVertexIndices, &_err)) { + PUSH_ERROR_AND_RETURN( + "Failed to triangulate vertopacitiesex_colors attribute."); + } + } + + dst.triangulatedFaceVertexCounts = std::move(triangulatedFaceVertexCounts); + dst.triangulatedFaceVertexIndices = + std::move(triangulatedFaceVertexIndices); + + dst.triangulatedToOrigFaceVertexIndexMap = + std::move(triangulatedToOrigFaceVertexIndexMap); + dst.triangulatedFaceCounts = std::move(triangulatedFaceCounts); + } + + // + // 5. Vertex skin weights(jointIndex and jointWeights) + // + if (mesh.has_primvar("skel:jointIndices") && + mesh.has_primvar("skel:jointWeights")) { + DCOUT("Convert skin weights"); + GeomPrimvar jointIndices; + GeomPrimvar jointWeights; + + if (!GetGeomPrimvar(env.stage, &mesh, "skel:jointIndices", &jointIndices, + &_err)) { + return false; + } + + if (!GetGeomPrimvar(env.stage, &mesh, "skel:jointWeights", &jointWeights, + &_err)) { + return false; + } + + // interpolation must be 'vertex' + if (!jointIndices.has_interpolation()) { + PUSH_ERROR_AND_RETURN( + fmt::format("`skel:jointIndices` primvar must author `interpolation` " + "metadata(and set it to `vertex`)")); + } + + // TODO: Disallow Varying? + if ((jointIndices.get_interpolation() != Interpolation::Vertex) && + (jointIndices.get_interpolation() != Interpolation::Varying)) { + PUSH_ERROR_AND_RETURN( + fmt::format("`skel:jointIndices` primvar must use `vertex` for " + "`interpolation` metadata, but got `{}`.", + to_string(jointIndices.get_interpolation()))); + } + + if (!jointWeights.has_interpolation()) { + PUSH_ERROR_AND_RETURN( + fmt::format("`skel:jointWeights` primvar must author `interpolation` " + "metadata(and set it to `vertex`)")); + } + + // TODO: Disallow Varying? + if ((jointWeights.get_interpolation() != Interpolation::Vertex) && + (jointWeights.get_interpolation() != Interpolation::Varying)) { + PUSH_ERROR_AND_RETURN( + fmt::format("`skel:jointWeights` primvar must use `vertex` for " + "`interpolation` metadata, but got `{}`.", + to_string(jointWeights.get_interpolation()))); + } + + uint32_t jointIndicesElementSize = jointIndices.get_elementSize(); + uint32_t jointWeightsElementSize = jointWeights.get_elementSize(); + + if (jointIndicesElementSize == 0) { + PUSH_ERROR_AND_RETURN(fmt::format( + "`elementSize` metadata of `skel:jointIndices` is zero.")); + } + + if (jointWeightsElementSize == 0) { + PUSH_ERROR_AND_RETURN(fmt::format( + "`elementSize` metadata of `skel:jointWeights` is zero.")); + } + + if (jointIndicesElementSize > env.mesh_config.max_skin_elementSize) { + PUSH_ERROR_AND_RETURN(fmt::format( + "`elementSize` {} of `skel:jointIndices` too large. Max allowed is " + "set to {}", + jointIndicesElementSize, env.mesh_config.max_skin_elementSize)); + } + + if (jointWeightsElementSize > env.mesh_config.max_skin_elementSize) { + PUSH_ERROR_AND_RETURN(fmt::format( + "`elementSize` {} of `skel:jointWeights` too large. Max allowed is " + "set to {}", + jointWeightsElementSize, env.mesh_config.max_skin_elementSize)); + } + + if (jointIndicesElementSize != jointWeightsElementSize) { + PUSH_ERROR_AND_RETURN( + fmt::format("`elementSize` {} of `skel:jointIndices` must equal to " + "`elementSize` {} of `skel:jointWeights`", + jointIndicesElementSize, jointWeightsElementSize)); + } + + std::vector jointIndicesArray; + if (!jointIndices.flatten_with_indices(env.timecode, &jointIndicesArray, + env.tinterp)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to flatten Indexed Primvar `skel:jointIndices`. " + "Ensure `skel:jointIndices` is type `int[]`")); + } + + std::vector jointWeightsArray; + if (!jointWeights.flatten_with_indices(env.timecode, &jointWeightsArray, + env.tinterp)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to flatten Indexed Primvar `skel:jointWeights`. " + "Ensure `skel:jointWeights` is type `float[]`")); + } + + if (jointIndicesArray.size() != jointWeightsArray.size()) { + PUSH_ERROR_AND_RETURN( + fmt::format("`skel:jointIndices` nitems {} must be equal to " + "`skel:jointWeights` ntems {}", + jointIndicesArray.size(), jointWeightsArray.size())); + } + + if (jointIndicesArray.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("`skel:jointIndices` is empty array.")); + } + + // TODO: Validate jointIndex. + + dst.joint_and_weights.jointIndices = jointIndicesArray; + dst.joint_and_weights.jointWeights = jointWeightsArray; + dst.joint_and_weights.elementSize = int(jointIndicesElementSize); + + if (mesh.skeleton.has_value()) { + Path skelPath; + + if (mesh.skeleton.value().is_path()) { + skelPath = mesh.skeleton.value().targetPath; + } else if (mesh.skeleton.value().is_pathvector()) { + // Use the first tone + if (mesh.skeleton.value().targetPathVector.size()) { + skelPath = mesh.skeleton.value().targetPathVector[0]; + } else { + PUSH_WARN("`skel:skeleton` has invalid definition."); + } + } else { + PUSH_WARN("`skel:skeleton` has invalid definition."); + } + + if (skelPath.is_valid()) { + // TODO + } + } + + // geomBindTransform(optional). + if (mesh.has_primvar("skel:geomBindTransform")) { + GeomPrimvar bindTransformPvar; + + if (!GetGeomPrimvar(env.stage, &mesh, "skel:geomBindTransform", + &bindTransformPvar, &_err)) { + return false; + } + + value::matrix4d bindTransform; + if (!bindTransformPvar.get_value(&bindTransform)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to get `skel:geomBindTransform` attribute. " + "Ensure `skel:geomBindTransform` is type `matrix4d`")); + } + + dst.joint_and_weights.geomBindTransform = bindTransform; + } + } + + // + // 6. BlendShapes + // + // NOTE: (Built-in) BlendShape attributes are per-point, so it is not + // affected by triangulation and single-indexable indices build. + // + for (const auto &it : blendshapes) { + const std::string &bs_path = it.first; + const BlendShape *bs = it.second; + + if (!bs) { + continue; + } + + // + // TODO: in-between attribs + // + + std::vector vertex_indices; + std::vector normal_offsets; + std::vector vertex_offsets; + + bs->pointIndices.get_value(&vertex_indices); + bs->normalOffsets.get_value(&normal_offsets); + bs->offsets.get_value(&vertex_offsets); + + ShapeTarget shapeTarget; + shapeTarget.abs_path = bs_path; + shapeTarget.prim_name = bs->name; + shapeTarget.display_name = bs->metas().displayName.value_or(""); + + if (vertex_indices.empty()) { + PUSH_WARN( + fmt::format("`pointIndices` in BlendShape `{}` is not authored or " + "empty. Skipping.", + bs->name)); + } + + // Check if index is valid. + std::vector indices; + indices.resize(vertex_indices.size()); + + for (size_t i = 0; i < vertex_indices.size(); i++) { + if (vertex_indices[i] < 0) { + PUSH_ERROR_AND_RETURN(fmt::format( + "negative index in `pointIndices`. Prim path: `{}`", bs_path)); + } + + if (uint32_t(vertex_indices[i]) > dst.points.size()) { + PUSH_ERROR_AND_RETURN( + fmt::format("pointIndices[{}] {} exceeds the number of points in " + "GeomMesh {}. Prim path: `{}`", + i, vertex_indices[i], dst.points.size(), bs_path)); + } + + indices[i] = uint32_t(vertex_indices[i]); + } + shapeTarget.pointIndices = indices; + + if (vertex_offsets.size() && + (vertex_offsets.size() == vertex_indices.size())) { + shapeTarget.pointOffsets.resize(vertex_offsets.size()); + memcpy(shapeTarget.pointOffsets.data(), vertex_offsets.data(), + sizeof(value::normal3f) * vertex_offsets.size()); + } + + if (normal_offsets.size() && + (normal_offsets.size() == vertex_indices.size())) { + shapeTarget.normalOffsets.resize(normal_offsets.size()); + memcpy(shapeTarget.normalOffsets.data(), normal_offsets.data(), + sizeof(value::normal3f) * normal_offsets.size()); + } + + // TODO inbetweens + + // TODO: key duplicate check + dst.targets[bs->name] = shapeTarget; + DCOUT("Converted blendshape target: " << bs->name); + } + + // + // 7. Compute normals + // + // Compute normals when normals is not present or compute tangents + // requiested but normals is not present. Normals are computed with + // 'vertex' variability to compute smooth normals for shared vertex. + // + // When triangulated, normals are computed for triangulated mesh. + // + bool compute_normals = + (env.mesh_config.compute_normals && dst.normals.empty()); + bool compute_tangents = + (env.mesh_config.compute_tangents_and_binormals && + (dst.binormals.empty() == 0 && dst.tangents.empty() == 0)); + + if (compute_normals || (compute_tangents && dst.normals.empty())) { + DCOUT("Compute normals"); + std::vector normals; + if (!ComputeNormals(dst.points, dst.faceVertexCounts(), + dst.faceVertexIndices(), normals, &_err)) { + DCOUT("compute normals failed."); + return false; + } + + dst.normals.set_buffer(reinterpret_cast(normals.data()), + normals.size() * sizeof(vec3)); + dst.normals.elementSize = 1; + dst.normals.variability = VertexVariability::Vertex; + dst.normals.format = VertexAttributeFormat::Vec3; + dst.normals.stride = 0; + dst.normals.indices.clear(); + + if (!is_single_indexable) { + auto result = VertexToFaceVarying( + dst.normals.get_data(), dst.normals.stride_bytes(), + dst.faceVertexCounts(), dst.faceVertexIndices()); + if (!result) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Convert vertex/varying `normals` attribute to failed: {}", + result.error())); + } + dst.normals.data = result.value(); + dst.normals.variability = VertexVariability::FaceVarying; + } + } + + // + // 8. Build indices + // + if (env.mesh_config.build_vertex_indices && (!is_single_indexable)) { + DCOUT("Build vertex indices"); + + if (!BuildVertexIndicesImpl(dst)) { + return false; + } + + is_single_indexable = true; + } + + // + // 8. Compute tangents. + // + if (compute_tangents) { + DCOUT("Compute tangents."); + std::vector texcoords; + std::vector normals; + + // TODO: Support arbitrary slotID + if (!dst.texcoords.count(0)) { + PUSH_ERROR_AND_RETURN( + "texcoord is required to compute tangents/binormals.\n"); + } + + texcoords.resize(dst.texcoords[0].vertex_count()); + normals.resize(dst.normals.vertex_count()); + + memcpy(texcoords.data(), dst.texcoords[0].buffer(), + dst.texcoords[0].num_bytes()); + memcpy(normals.data(), dst.normals.buffer(), dst.normals.num_bytes()); + + std::vector tangents; + std::vector binormals; + std::vector vertex_indices; + + if (!ComputeTangentsAndBinormals(dst.points, dst.faceVertexCounts(), + dst.faceVertexIndices(), texcoords, + normals, !is_single_indexable, &tangents, + &binormals, &vertex_indices, &_err)) { + PUSH_ERROR_AND_RETURN("Failed to compute tangents/binormals."); + } + + // 1. Firstly, always convert tangents/binormals to 'facevarying' + // variability + { + std::vector facevarying_tangents; + std::vector facevarying_binormals; + facevarying_tangents.assign(vertex_indices.size(), {0.0f, 0.0f, 0.0f}); + facevarying_binormals.assign(vertex_indices.size(), {0.0f, 0.0f, 0.0f}); + for (size_t i = 0; i < vertex_indices.size(); i++) { + facevarying_tangents[i] = tangents[vertex_indices[i]]; + facevarying_binormals[i] = binormals[vertex_indices[i]]; + } + + dst.tangents.data.resize(facevarying_tangents.size() * sizeof(vec3)); + memcpy(dst.tangents.data.data(), facevarying_tangents.data(), + facevarying_tangents.size() * sizeof(vec3)); + + dst.tangents.format = VertexAttributeFormat::Vec3; + dst.tangents.stride = 0; + dst.tangents.elementSize = 1; + dst.tangents.variability = VertexVariability::FaceVarying; + + dst.binormals.data.resize(facevarying_binormals.size() * sizeof(vec3)); + memcpy(dst.binormals.data.data(), facevarying_binormals.data(), + facevarying_binormals.size() * sizeof(vec3)); + + dst.binormals.format = VertexAttributeFormat::Vec3; + dst.binormals.stride = 0; + dst.binormals.elementSize = 1; + dst.binormals.variability = VertexVariability::FaceVarying; + } + + // 2. Build single vertex indices if `build_vertex_indices` is true. + if (env.mesh_config.build_vertex_indices) { + if (!BuildVertexIndicesImpl(dst)) { + return false; + } + is_single_indexable = true; + } + } + + dst.prim_name = mesh.name; + dst.abs_path = abs_path.full_path_name(); + dst.display_name = mesh.metas().displayName.value_or(""); + + (*dstMesh) = std::move(dst); + + return true; +} + +namespace { + +// Convert UsdTransform2d -> PrimvarReader_float2 shader network. +nonstd::expected ConvertTexTransform2d( + const Stage &stage, const Path &tx_abs_path, const UsdTransform2d &tx, + UVTexture *tex_out, double timecode) { + float rotation; // in angles + if (!tx.rotation.get_value().get(timecode, &rotation)) { + return nonstd::make_unexpected( + fmt::format("Failed to retrieve rotation attribute from {}\n", + tx_abs_path.full_path_name())); + } + + value::float2 scale; + if (!tx.scale.get_value().get(timecode, &scale)) { + return nonstd::make_unexpected( + fmt::format("Failed to retrieve scale attribute from {}\n", + tx_abs_path.full_path_name())); + } + + value::float2 translation; + if (!tx.translation.get_value().get(timecode, &translation)) { + return nonstd::make_unexpected( + fmt::format("Failed to retrieve translation attribute from {}\n", + tx_abs_path.full_path_name())); + } + + // must be authored and connected to PrimvarReader. + if (!tx.in.authored()) { + return nonstd::make_unexpected("`inputs:in` must be authored.\n"); + } + + if (!tx.in.is_connection()) { + return nonstd::make_unexpected("`inputs:in` must be a connection.\n"); + } + + const auto &paths = tx.in.get_connections(); + if (paths.size() != 1) { + return nonstd::make_unexpected( + "`inputs:in` must be a single connection Path.\n"); + } + + std::string prim_part = paths[0].prim_part(); + std::string prop_part = paths[0].prop_part(); + + if (prop_part != "outputs:result") { + return nonstd::make_unexpected( + "`inputs:in` connection Path's property part must be " + "`outputs:result`\n"); + } + + std::string err; + + const Prim *pprim{nullptr}; + if (!stage.find_prim_at_path(Path(prim_part, ""), pprim, &err)) { + return nonstd::make_unexpected(fmt::format( + "`inputs:in` connection Path not found in the Stage. {}\n", prim_part)); + } + + if (!pprim) { + return nonstd::make_unexpected( + fmt::format("[InternalError] Prim is nullptr: {}\n", prim_part)); + } + + const Shader *pshader = pprim->as(); + if (!pshader) { + return nonstd::make_unexpected( + fmt::format("{} must be Shader Prim, but got {}\n", prim_part, + pprim->prim_type_name())); + } + + const UsdPrimvarReader_float2 *preader = + pshader->value.as(); + if (!preader) { + return nonstd::make_unexpected(fmt::format( + "Shader {} must be UsdPrimvarReader_float2 type, but got {}(internal type {})\n", + prim_part, pshader->info_id, pshader->value.type_name())); + } + + // Get value producing attribute(i.e, follow .connection and return + // terminal Attribute value) + //value::token varname; + + // 'string' for inputs:varname preferred. + std::string varname; +#if 0 + if (!tydra::EvaluateShaderAttribute(stage, *pshader, "inputs:varname", + &varname, &err)) { + return nonstd::make_unexpected( + fmt::format("Failed to evaluate UsdPrimvarReader_float2's " + "inputs:varname: {}\n", + err)); + } +#else + TerminalAttributeValue attr; + if (!tydra::EvaluateAttribute(stage, *pprim, "inputs:varname", &attr, &err)) { + return nonstd::make_unexpected( + "`inputs:varname` evaluation failed: " + err + "\n"); + } + if (auto pvt = attr.as()) { + varname = pvt->str(); + } else if (auto pvs = attr.as()) { + varname = *pvs; + } else if (auto pvsd = attr.as()) { + varname = (*pvsd).value; + } else { + return nonstd::make_unexpected( + "`inputs:varname` must be `token` or `string` type, but got " + attr.type_name() + + "\n"); + } + if (varname.empty()) { + return nonstd::make_unexpected("`inputs:varname` is empty token\n"); + } + DCOUT("inputs:varname = " << varname); +#endif + + // Build transform matrix. + // https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_transform + // Since USD uses post-multiply, + // + // matrix = scale * rotate * translate + // + { + mat3 s; + s.set_scale(scale[0], scale[1], 1.0f); + + mat3 r = mat3::identity(); + + r.m[0][0] = std::cos(math::radian(rotation)); + r.m[0][1] = std::sin(math::radian(rotation)); + + r.m[1][0] = -std::sin(math::radian(rotation)); + r.m[1][1] = std::cos(math::radian(rotation)); + + mat3 t = mat3::identity(); + t.set_translation(translation[0], translation[1], 1.0f); + + tex_out->transform = s * r * t; + } + + tex_out->tx_rotation = rotation; + tex_out->tx_translation = translation; + tex_out->tx_scale = scale; + tex_out->has_transform2d = true; + + tex_out->varname_uv = varname; + + return true; +} + +template +nonstd::expected GetConnectedUVTexture( + const Stage &stage, const TypedAnimatableAttributeWithFallback &src, + Path *tex_abs_path, const UsdUVTexture **dst, const Shader **shader_out) { + if (!dst) { + return nonstd::make_unexpected("[InternalError] dst is nullptr.\n"); + } + + if (!src.is_connection()) { + return nonstd::make_unexpected("Attribute must be connection.\n"); + } + + if (src.get_connections().size() != 1) { + return nonstd::make_unexpected( + "Attribute connections must be single connection Path.\n"); + } + + // + // Example: color3f inputs:diffuseColor.connect = + // + // => path.prim_part : /path/to/tex + // => path.prop_part : outputs:rgb + // + + const Path &path = src.get_connections()[0]; + + const std::string prim_part = path.prim_part(); + const std::string prop_part = path.prop_part(); + + // NOTE: no `outputs:rgba` in the spec. + constexpr auto kOutputsRGB = "outputs:rgb"; + constexpr auto kOutputsR = "outputs:r"; + constexpr auto kOutputsG = "outputs:g"; + constexpr auto kOutputsB = "outputs:b"; + constexpr auto kOutputsA = "outputs:a"; + + if (prop_part == kOutputsRGB) { + // ok + } else if (prop_part == kOutputsR) { + // ok + } else if (prop_part == kOutputsG) { + // ok + } else if (prop_part == kOutputsB) { + // ok + } else if (prop_part == kOutputsA) { + // ok + } else { + return nonstd::make_unexpected(fmt::format( + "connection Path's property part must be `{}`, `{}`, `{}` or `{}` " + "for " + "UsdUVTexture, but got `{}`\n", + kOutputsRGB, kOutputsR, kOutputsG, kOutputsB, kOutputsA, prop_part)); + } + + const Prim *prim{nullptr}; + std::string err; + if (!stage.find_prim_at_path(Path(prim_part, ""), prim, &err)) { + return nonstd::make_unexpected( + fmt::format("Prim {} not found in the Stage: {}\n", prim_part, err)); + } + + if (!prim) { + return nonstd::make_unexpected("[InternalError] Prim ptr is null.\n"); + } + + if (tex_abs_path) { + (*tex_abs_path) = Path(prim_part, ""); + } + + if (const Shader *pshader = prim->as()) { + if (const UsdUVTexture *ptex = pshader->value.as()) { + DCOUT("ptex = " << ptex); + (*dst) = ptex; + + if (shader_out) { + (*shader_out) = pshader; + } + + return true; + } + } + + return nonstd::make_unexpected( + fmt::format("Prim {} must be `Shader` Prim type, but got `{}`", prim_part, + prim->prim_type_name())); +} + +} // namespace + +// Convert UsdUVTexture shader node. +// @return true upon conversion success(textures.back() contains the converted +// UVTexture) +// +// Possible network configuration +// +// - UsdUVTexture -> UsdPrimvarReader +// - UsdUVTexture -> UsdTransform2d -> UsdPrimvarReader +bool RenderSceneConverter::ConvertUVTexture(const RenderSceneConverterEnv &env, + const Path &tex_abs_path, + const AssetInfo &assetInfo, + const UsdUVTexture &texture, + UVTexture *tex_out) { + DCOUT("ConvertUVTexture " << tex_abs_path); + + if (!tex_out) { + PUSH_ERROR_AND_RETURN("tex_out arg is nullptr."); + } + std::string err; + + UVTexture tex; + + if (!texture.file.authored()) { + PUSH_ERROR_AND_RETURN(fmt::format("`asset:file` is not authored. Path = {}", + tex_abs_path.prim_part())); + } + + value::AssetPath assetPath; + if (auto apath = texture.file.get_value()) { + if (!apath.value().get(env.timecode, &assetPath)) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Failed to get `asset:file` value from Path {} at time {}", + tex_abs_path.prim_part(), env.timecode)); + } + } else { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to get `asset:file` value from Path {}", + tex_abs_path.prim_part())); + } + + // TextureImage and BufferData + { + TextureImage texImage; + BufferData assetImageBuffer; + + // Texel data is treated as byte array + assetImageBuffer.componentType = ComponentType::UInt8; + + if (env.scene_config.load_texture_assets) { + DCOUT("load texture : " << assetPath.GetAssetPath()); + std::string warn; + + TextureImageLoaderFunction tex_loader_fun = + env.material_config.texture_image_loader_function; + + if (!tex_loader_fun) { + tex_loader_fun = DefaultTextureImageLoaderFunction; + } + + bool tex_ok = tex_loader_fun( + assetPath, assetInfo, env.asset_resolver, &texImage, + &assetImageBuffer.data, + env.material_config.texture_image_loader_function_userdata, &warn, + &err); + + if (warn.size()) { + DCOUT("WARN: " << warn); + PushWarn(warn); + } + + if (!tex_ok && !env.material_config.allow_texture_load_failure) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to load texture image: `{}` err = {}", assetPath.GetAssetPath(), err)); + } + + + if (err.size()) { + // report as warn. + PUSH_WARN(fmt::format("Failed to load texture image: `{}`. Skip loading. reason = {} ", assetPath.GetAssetPath(), err)); + } + + // store unresolved asset path. + texImage.asset_identifier = assetPath.GetAssetPath(); + + } else { + // store resolved asset path. + texImage.asset_identifier = + env.asset_resolver.resolve(assetPath.GetAssetPath()); + } + + // colorSpace. + // First look into `colorSpace` metadata of asset, then + // look into `inputs:sourceColorSpace' attribute. + // When both `colorSpace` metadata and `inputs:sourceColorSpace' attribute + // exists, `inputs:sourceColorSpace` wins. + // FIXME: Change to `colorSpace` metadata supersedes 'inputs:sourceColorSpace'? + bool inferColorSpaceFailed = false; + if (texture.file.metas().has_colorSpace()) { + ColorSpace cs; + value::token cs_token = texture.file.metas().get_colorSpace(); + if (InferColorSpace(cs_token, &cs)) { + texImage.usdColorSpace = cs; + DCOUT("Inferred colorSpace: " << to_string(cs)); + } else { + inferColorSpaceFailed = true; + } + } + + bool sourceColorSpaceSet = false; + { + if (texture.sourceColorSpace.authored()) { + UsdUVTexture::SourceColorSpace cs; + if (texture.sourceColorSpace.get_value().get(env.timecode, &cs)) { + if (cs == UsdUVTexture::SourceColorSpace::SRGB) { + texImage.usdColorSpace = tydra::ColorSpace::sRGB; + sourceColorSpaceSet = true; + } else if (cs == UsdUVTexture::SourceColorSpace::Raw) { + texImage.usdColorSpace = tydra::ColorSpace::Linear; + sourceColorSpaceSet = true; + } else if (cs == UsdUVTexture::SourceColorSpace::Auto) { + // TODO: Read colorspace from a file. + if ((texImage.assetTexelComponentType == ComponentType::UInt8) || + (texImage.assetTexelComponentType == ComponentType::Int8)) { + texImage.usdColorSpace = tydra::ColorSpace::sRGB; + sourceColorSpaceSet = true; + } else { + texImage.usdColorSpace = tydra::ColorSpace::Linear; + sourceColorSpaceSet = true; + } + } + } + } + } + + if (!sourceColorSpaceSet && inferColorSpaceFailed) { + value::token cs_token = texture.file.metas().get_colorSpace(); + PUSH_ERROR_AND_RETURN( + fmt::format("Invalid or unknown colorSpace metadataum: {}. Please " + "report an issue to TinyUSDZ github repo.", + cs_token.str())); + } + + BufferData imageBuffer; + + // Linearlization and widen texel bit depth if required. + if (env.material_config.linearize_color_space) { + DCOUT("linearlize colorspace."); + size_t width = size_t(texImage.width); + size_t height = size_t(texImage.height); + size_t channels = size_t(texImage.channels); + + if (channels > 4) { + PUSH_ERROR_AND_RETURN( + fmt::format("TODO: Multiband color channels(5 or more) are not " + "supported(yet).")); + } + + if (assetImageBuffer.componentType == tydra::ComponentType::UInt8) { + if (texImage.usdColorSpace == tydra::ColorSpace::sRGB) { + if (env.material_config.preserve_texel_bitdepth) { + // u8 sRGB -> u8 Linear + imageBuffer.componentType = tydra::ComponentType::UInt8; + + bool ret = srgb_8bit_to_linear_8bit( + assetImageBuffer.data, width, height, channels, + /* channel stride */ channels, &imageBuffer.data); + if (!ret) { + PUSH_ERROR_AND_RETURN( + "Failed to convert sRGB u8 image to Linear u8 image."); + } + + } else { + DCOUT("u8 sRGB -> fp32 linear."); + // u8 sRGB -> fp32 Linear + imageBuffer.componentType = tydra::ComponentType::Float; + + std::vector buf; + bool ret = srgb_8bit_to_linear_f32( + assetImageBuffer.data, width, height, channels, + /* channel stride */ channels, &buf); + if (!ret) { + PUSH_ERROR_AND_RETURN( + "Failed to convert sRGB u8 image to Linear f32 image."); + } + + DCOUT("sz = " << buf.size()); + imageBuffer.data.resize(buf.size() * sizeof(float)); + memcpy(imageBuffer.data.data(), buf.data(), + sizeof(float) * buf.size()); + } + + texImage.colorSpace = tydra::ColorSpace::Linear; + + } else if (texImage.usdColorSpace == tydra::ColorSpace::Linear) { + if (env.material_config.preserve_texel_bitdepth) { + // no op. + imageBuffer = std::move(assetImageBuffer); + + } else { + // u8 -> fp32 + imageBuffer.componentType = tydra::ComponentType::Float; + + std::vector buf; + bool ret = u8_to_f32_image(assetImageBuffer.data, width, height, + channels, &buf); + if (!ret) { + PUSH_ERROR_AND_RETURN("Failed to convert u8 image to f32 image."); + } + + imageBuffer.data.resize(buf.size() * sizeof(float)); + memcpy(imageBuffer.data.data(), buf.data(), + sizeof(float) * buf.size()); + } + + texImage.colorSpace = tydra::ColorSpace::Linear; + + } else { + PUSH_ERROR(fmt::format("TODO: Color space {}", + to_string(texImage.usdColorSpace))); + } + + } else if (assetImageBuffer.componentType == + tydra::ComponentType::Float) { + // ignore preserve_texel_bitdepth + + if (texImage.usdColorSpace == tydra::ColorSpace::sRGB) { + // srgb f32 -> linear f32 + std::vector in_buf; + std::vector out_buf; + in_buf.resize(assetImageBuffer.data.size() / sizeof(float)); + memcpy(in_buf.data(), assetImageBuffer.data.data(), + in_buf.size() * sizeof(float)); + + out_buf.resize(assetImageBuffer.data.size() / sizeof(float)); + + bool ret = + srgb_f32_to_linear_f32(in_buf, width, height, channels, + /* channel stride */ channels, &out_buf); + + imageBuffer.data.resize(assetImageBuffer.data.size()); + memcpy(imageBuffer.data.data(), out_buf.data(), + imageBuffer.data.size()); + + if (!ret) { + PUSH_ERROR_AND_RETURN( + "Failed to convert sRGB f32 image to Linear f32 image."); + } + + } else if (texImage.usdColorSpace == tydra::ColorSpace::Linear) { + // no op + imageBuffer = std::move(assetImageBuffer); + + } else { + PUSH_ERROR(fmt::format("TODO: Color space {}", + to_string(texImage.usdColorSpace))); + } + + } else { + PUSH_ERROR(fmt::format("TODO: asset texture texel format {}", + to_string(assetImageBuffer.componentType))); + } + + } else { + // Same color space. + DCOUT("assetImageBuffer.sz = " << assetImageBuffer.data.size()); + + if (assetImageBuffer.componentType == tydra::ComponentType::UInt8) { + if (env.material_config.preserve_texel_bitdepth) { + // Do nothing. + imageBuffer = std::move(assetImageBuffer); + + } else { + size_t width = size_t(texImage.width); + size_t height = size_t(texImage.height); + size_t channels = size_t(texImage.channels); + + // u8 to f32, but no sRGB -> linear conversion(this would break + // UsdPreviewSurface's spec though) + PUSH_WARN( + "8bit sRGB texture is converted to fp32 sRGB texture(without " + "linearlization)"); + std::vector buf; + bool ret = u8_to_f32_image(assetImageBuffer.data, width, height, + channels, &buf); + if (!ret) { + PUSH_ERROR_AND_RETURN("Failed to convert u8 image to f32 image."); + } + imageBuffer.componentType = tydra::ComponentType::Float; + + imageBuffer.data.resize(buf.size() * sizeof(float)); + memcpy(imageBuffer.data.data(), buf.data(), + sizeof(float) * buf.size()); + } + + texImage.colorSpace = texImage.usdColorSpace; + + } else if (assetImageBuffer.componentType == + tydra::ComponentType::Float) { + // ignore preserve_texel_bitdepth + + // f32 to f32, so no op + imageBuffer = std::move(assetImageBuffer); + + } else { + PUSH_ERROR(fmt::format("TODO: asset texture texel format {}", + to_string(assetImageBuffer.componentType))); + } + } + + // Assign buffer id + texImage.buffer_id = int64_t(buffers.size()); + + // TODO: Share image data as much as possible. + // e.g. Texture A and B uses same image file, but texturing parameter is + // different. + buffers.emplace_back(imageBuffer); + + tex.texture_image_id = int64_t(images.size()); + + images.emplace_back(texImage); + + std::stringstream ss; + ss << "Loaded texture image " << assetPath.GetAssetPath() + << " : buffer_id " + std::to_string(texImage.buffer_id) << "\n"; + ss << " width x height x components " << texImage.width << " x " + << texImage.height << " x " << texImage.channels << "\n"; + ss << " colorSpace " << tinyusdz::tydra::to_string(texImage.colorSpace) + << "\n"; + PushInfo(ss.str()); + } + + // + // Set outputChannel + // + if (texture.outputsRGB.authored()) { + tex.outputChannel = UVTexture::Channel::RGB; + } else if (texture.outputsA.authored()) { + tex.outputChannel = UVTexture::Channel::A; + } else if (texture.outputsR.authored()) { + tex.outputChannel = UVTexture::Channel::R; + } else if (texture.outputsG.authored()) { + tex.outputChannel = UVTexture::Channel::G; + } else if (texture.outputsB.authored()) { + tex.outputChannel = UVTexture::Channel::B; + } else { + PUSH_WARN("No valid output channel attribute authored. Default to RGB"); + tex.outputChannel = UVTexture::Channel::RGB; + } + + // + // Convert other UVTexture parameters + // + + if (texture.bias.authored()) { + tex.bias = texture.bias.get_value(); + } + + if (texture.scale.authored()) { + tex.scale = texture.scale.get_value(); + } + + if (texture.st.authored()) { + if (texture.st.is_connection()) { + const auto &paths = texture.st.get_connections(); + if (paths.size() != 1) { + PUSH_ERROR_AND_RETURN( + "UsdUVTexture inputs:st connection must be single Path."); + } + const Path &path = paths[0]; + + const Prim *readerPrim{nullptr}; + if (!env.stage.find_prim_at_path(Path(path.prim_part(), ""), readerPrim, + &err)) { + PUSH_ERROR_AND_RETURN( + "UsdUVTexture inputs:st connection targetPath not found in the " + "Stage: " + + err); + } + + if (!readerPrim) { + PUSH_ERROR_AND_RETURN( + "[InternlError] Invalid Prim connected to inputs:st"); + } + + const Shader *pshader = readerPrim->as(); + if (!pshader) { + PUSH_ERROR_AND_RETURN( + fmt::format("UsdUVTexture inputs:st connected Prim must be " + "Shader Prim, but got {} Prim", + readerPrim->prim_type_name())); + } + + // currently UsdTranform2d or PrimvarReaer_float2 only for inputs:st + if (const UsdPrimvarReader_float2 *preader = + pshader->value.as()) { + if (!preader) { + PUSH_ERROR_AND_RETURN( + fmt::format("Shader's info:id must be UsdPrimvarReader_float2, " + "but got {}", + pshader->info_id)); + } + + // Get value producing attribute(i.e, follow .connection and return + // terminal Attribute value) + std::string varname; + TerminalAttributeValue attr; + if (!tydra::EvaluateAttribute(env.stage, *readerPrim, "inputs:varname", + &attr, &err)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to evaluate UsdPrimvarReader_float2's " + "inputs:varname.\n{}", + err)); + } + + if (auto pv = attr.as()) { + varname = (*pv).str(); + } else if (auto pvs = attr.as()) { + varname = (*pvs); + } else if (auto pvsd = attr.as()) { + varname = (*pvsd).value; + } else { + PUSH_ERROR_AND_RETURN( + "`inputs:varname` must be `string` or `token` type, but got " + + attr.type_name()); + } + if (varname.empty()) { + PUSH_ERROR_AND_RETURN("`inputs:varname` is empty token."); + } + DCOUT("inputs:varname = " << varname); + + tex.varname_uv = varname; + } else if (const UsdTransform2d *ptransform = + pshader->value.as()) { + auto result = ConvertTexTransform2d(env.stage, path, *ptransform, &tex, + env.timecode); + if (!result) { + PUSH_ERROR_AND_RETURN(result.error()); + } + } else { + PUSH_ERROR_AND_RETURN( + "Unsupported Shader type for `inputs:st` connection: " + + pshader->info_id + "\n"); + } + + } else { + Animatable fallbacks = texture.st.get_value(); + value::texcoord2f uv; + if (fallbacks.get(env.timecode, &uv)) { + tex.fallback_uv[0] = uv[0]; + tex.fallback_uv[1] = uv[1]; + } else { + // TODO: report warning. + PUSH_WARN("Failed to get fallback `st` texcoord attribute."); + } + } + } + + if (texture.wrapS.authored()) { + tinyusdz::UsdUVTexture::Wrap wrap; + + if (!texture.wrapS.get_value().get(env.timecode, &wrap)) { + PUSH_ERROR_AND_RETURN("Invalid UsdUVTexture inputs:wrapS value."); + } + + if (wrap == UsdUVTexture::Wrap::Repeat) { + tex.wrapS = UVTexture::WrapMode::REPEAT; + } else if (wrap == UsdUVTexture::Wrap::Mirror) { + tex.wrapS = UVTexture::WrapMode::MIRROR; + } else if (wrap == UsdUVTexture::Wrap::Clamp) { + tex.wrapS = UVTexture::WrapMode::CLAMP_TO_EDGE; + } else if (wrap == UsdUVTexture::Wrap::Black) { + tex.wrapS = UVTexture::WrapMode::CLAMP_TO_BORDER; + } else { + tex.wrapS = UVTexture::WrapMode::CLAMP_TO_EDGE; + } + } + + if (texture.wrapT.authored()) { + tinyusdz::UsdUVTexture::Wrap wrap; + + if (!texture.wrapT.get_value().get(env.timecode, &wrap)) { + PUSH_ERROR_AND_RETURN("Invalid UsdUVTexture inputs:wrapT value."); + } + + if (wrap == UsdUVTexture::Wrap::Repeat) { + tex.wrapT = UVTexture::WrapMode::REPEAT; + } else if (wrap == UsdUVTexture::Wrap::Mirror) { + tex.wrapT = UVTexture::WrapMode::MIRROR; + } else if (wrap == UsdUVTexture::Wrap::Clamp) { + tex.wrapT = UVTexture::WrapMode::CLAMP_TO_EDGE; + } else if (wrap == UsdUVTexture::Wrap::Black) { + tex.wrapT = UVTexture::WrapMode::CLAMP_TO_BORDER; + } else { + tex.wrapT = UVTexture::WrapMode::CLAMP_TO_EDGE; + } + } + + DCOUT("Converted UVTexture."); + + (*tex_out) = tex; + return true; +} + +template +bool RenderSceneConverter::ConvertPreviewSurfaceShaderParam( + const RenderSceneConverterEnv &env, const Path &shader_abs_path, + const TypedAttributeWithFallback> ¶m, + const std::string ¶m_name, ShaderParam &dst_param) { + if (!param.authored()) { + return true; + } + + if (param.is_blocked()) { + PUSH_ERROR_AND_RETURN(fmt::format("{} attribute is blocked.", param_name)); + } else if (param.is_connection()) { + DCOUT(fmt::format("{] is attribute connection.", param_name)); + + const UsdUVTexture *ptex{nullptr}; + const Shader *pshader{nullptr}; + Path texPath; + auto result = + GetConnectedUVTexture(env.stage, param, &texPath, &ptex, &pshader); + + if (!result) { + PUSH_ERROR_AND_RETURN(result.error()); + } + + if (!ptex) { + PUSH_ERROR_AND_RETURN("[InternalError] ptex is nullptr."); + } + DCOUT("ptex = " << ptex->name); + + if (!pshader) { + PUSH_ERROR_AND_RETURN("[InternalError] pshader is nullptr."); + } + + DCOUT("Get connected UsdUVTexture Prim: " << texPath); + + UVTexture rtex; + const AssetInfo &assetInfo = pshader->metas().get_assetInfo(); + if (!ConvertUVTexture(env, texPath, assetInfo, *ptex, &rtex)) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Failed to convert UVTexture connected to {}", param_name)); + } + + uint64_t texId = textures.size(); + textures.push_back(rtex); + + textureMap.add(texId, shader_abs_path.prim_part() + "." + param_name); + + DCOUT(fmt::format("TexId {} = {}", + shader_abs_path.prim_part() + ".diffuseColor", texId)); + + dst_param.textureId = int32_t(texId); + + return true; + } else { + T val; + if (!param.get_value().get(env.timecode, &val)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to get {} at `default` timecode.", param_name)); + } + + dst_param.set_value(val); + + return true; + } +} + +bool RenderSceneConverter::ConvertPreviewSurfaceShader( + const RenderSceneConverterEnv &env, const Path &shader_abs_path, + const UsdPreviewSurface &shader, PreviewSurfaceShader *rshader_out) { + if (!rshader_out) { + PUSH_ERROR_AND_RETURN("rshader_out arg is nullptr."); + } + + PreviewSurfaceShader rshader; + + if (shader.useSpecularWorkflow.authored()) { + if (shader.useSpecularWorkflow.is_blocked()) { + PUSH_ERROR_AND_RETURN( + fmt::format("useSpecularWorkflow attribute is blocked.")); + } else if (shader.useSpecularWorkflow.is_connection()) { + PUSH_ERROR_AND_RETURN( + fmt::format("TODO: useSpecularWorkflow with connection.")); + } else { + int val; + if (!shader.useSpecularWorkflow.get_value().get(env.timecode, &val)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to get useSpcularWorkFlow value at time `{}`.", + env.timecode)); + } + + rshader.useSpecularWorkFlow = val ? true : false; + } + } + + if (!ConvertPreviewSurfaceShaderParam(env, shader_abs_path, + shader.diffuseColor, "diffuseColor", + rshader.diffuseColor)) { + return false; + } + + if (!ConvertPreviewSurfaceShaderParam(env, shader_abs_path, + shader.emissiveColor, "emissiveColor", + rshader.emissiveColor)) { + return false; + } + + if (!ConvertPreviewSurfaceShaderParam(env, shader_abs_path, + shader.specularColor, "specularColor", + rshader.specularColor)) { + return false; + } + + if (!ConvertPreviewSurfaceShaderParam(env, shader_abs_path, shader.normal, + "normal", rshader.normal)) { + return false; + } + + if (!ConvertPreviewSurfaceShaderParam(env, shader_abs_path, shader.roughness, + "roughness", rshader.roughness)) { + return false; + } + + if (!ConvertPreviewSurfaceShaderParam(env, shader_abs_path, shader.metallic, + "metallic", rshader.metallic)) { + return false; + } + + if (!ConvertPreviewSurfaceShaderParam(env, shader_abs_path, shader.clearcoat, + "clearcoat", rshader.clearcoat)) { + return false; + } + + if (!ConvertPreviewSurfaceShaderParam( + env, shader_abs_path, shader.clearcoatRoughness, "clearcoatRoughness", + rshader.clearcoatRoughness)) { + return false; + } + if (!ConvertPreviewSurfaceShaderParam(env, shader_abs_path, shader.opacity, + "opacity", rshader.opacity)) { + return false; + } + if (!ConvertPreviewSurfaceShaderParam( + env, shader_abs_path, shader.opacityThreshold, "opacityThreshold", + rshader.opacityThreshold)) { + return false; + } + + if (!ConvertPreviewSurfaceShaderParam(env, shader_abs_path, shader.ior, "ior", + rshader.ior)) { + return false; + } + + if (!ConvertPreviewSurfaceShaderParam(env, shader_abs_path, shader.occlusion, + "occlusion", rshader.occlusion)) { + return false; + } + + if (!ConvertPreviewSurfaceShaderParam(env, shader_abs_path, + shader.displacement, "displacement", + rshader.displacement)) { + return false; + } + + (*rshader_out) = rshader; + return true; +} + +bool RenderSceneConverter::ConvertMaterial(const RenderSceneConverterEnv &env, + const Path &mat_abs_path, + const tinyusdz::Material &material, + RenderMaterial *rmat_out) { + if (!rmat_out) { + PUSH_ERROR_AND_RETURN("rmat_out argument is nullptr."); + } + + RenderMaterial rmat; + rmat.abs_path = mat_abs_path.prim_part(); + rmat.name = mat_abs_path.element_name(); + DCOUT("rmat.abs_path = " << rmat.abs_path); + DCOUT("rmat.name = " << rmat.name); + std::string err; + Path surfacePath; + + // + // surface shader + { + if (material.surface.authored()) { + auto paths = material.surface.get_connections(); + DCOUT("paths = " << paths); + // must have single targetPath. + if (paths.size() != 1) { + PUSH_ERROR_AND_RETURN( + fmt::format("{}'s outputs:surface must be connection with single " + "target Path.\n", + mat_abs_path.full_path_name())); + } + surfacePath = paths[0]; + } else { + PUSH_ERROR_AND_RETURN( + fmt::format("{}'s outputs:surface isn't authored.\n", + mat_abs_path.full_path_name())); + } + + const Prim *shaderPrim{nullptr}; + if (!env.stage.find_prim_at_path( + Path(surfacePath.prim_part(), /* prop part */ ""), shaderPrim, + &err)) { + PUSH_ERROR_AND_RETURN(fmt::format( + "{}'s outputs:surface isn't connected to exising Prim path.\n", + mat_abs_path.full_path_name())); + } + + if (!shaderPrim) { + // this should not happen though. + PUSH_ERROR_AND_RETURN("[InternalError] invalid Shader Prim.\n"); + } + + const Shader *shader = shaderPrim->as(); + + if (!shader) { + PUSH_ERROR_AND_RETURN( + fmt::format("{}'s outputs:surface must be connected to Shader Prim, " + "but connected to `{}` Prim.\n", + shaderPrim->prim_type_name())); + } + + // Currently must be UsdPreviewSurface + const UsdPreviewSurface *psurface = shader->value.as(); + if (!psurface) { + PUSH_ERROR_AND_RETURN( + fmt::format("Shader's info:id must be UsdPreviewSurface, but got {}", + shader->info_id)); + } + + // prop part must be `outputs:surface` for now. + if (surfacePath.prop_part() != "outputs:surface") { + PUSH_ERROR_AND_RETURN( + fmt::format("{}'s outputs:surface connection must point to property " + "`outputs:surface`, but got `{}`", + mat_abs_path.full_path_name(), surfacePath.prop_part())); + } + + PreviewSurfaceShader pss; + if (!ConvertPreviewSurfaceShader(env, surfacePath, *psurface, &pss)) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Failed to convert UsdPreviewSurface : {}", surfacePath.prim_part())); + } + + rmat.surfaceShader = pss; + } + + DCOUT("Converted Material: " << mat_abs_path); + + (*rmat_out) = rmat; + return true; +} + +namespace { + +struct MeshVisitorEnv { + RenderSceneConverter *converter{nullptr}; + const RenderSceneConverterEnv *env{nullptr}; +}; + +bool MeshVisitor(const tinyusdz::Path &abs_path, const tinyusdz::Prim &prim, + const int32_t level, void *userdata, std::string *err) { + if (!userdata) { + if (err) { + (*err) += "userdata pointer must be filled."; + } + return false; + } + + MeshVisitorEnv *visitorEnv = reinterpret_cast(userdata); + + if (level > 1024 * 1024) { + if (err) { + (*err) += "Scene graph is too deep.\n"; + } + // Too deep + return false; + } + + if (const tinyusdz::GeomMesh *pmesh = prim.as()) { + // Collect GeomSubsets + // std::vector subsets = GetGeomSubsets(; + + DCOUT("Mesh: " << abs_path); + + // + // First convert Material assigned to GeomMesh. + // + // - If prim has GeomSubset with materialBind, convert it to per-face + // material. + // - If prim has materialBind, convert it to RenderMesh's material. + // + + auto ConvertBoundMaterial = [&](const Path &bound_material_path, + const tinyusdz::Material *bound_material, + int64_t &rmaterial_id) -> bool { + std::vector &rmaterials = + visitorEnv->converter->materials; + + const auto matIt = visitorEnv->converter->materialMap.find( + bound_material_path.full_path_name()); + + if (matIt != visitorEnv->converter->materialMap.s_end()) { + // Got material in the cache. + uint64_t mat_id = matIt->second; + if (mat_id >= visitorEnv->converter->materials + .size()) { // this should not happen though + if (err) { + (*err) += "Material index out-of-range.\n"; + } + return false; + } + + if (mat_id >= (std::numeric_limits::max)()) { + if (err) { + (*err) += "Material index too large.\n"; + } + return false; + } + + rmaterial_id = int64_t(mat_id); + + } else { + RenderMaterial rmat; + if (!visitorEnv->converter->ConvertMaterial(*visitorEnv->env, + bound_material_path, + *bound_material, &rmat)) { + if (err) { + (*err) += fmt::format("Material conversion failed: {}", + bound_material_path); + } + return false; + } + + // Assign new material ID + uint64_t mat_id = rmaterials.size(); + + if (mat_id >= (std::numeric_limits::max)()) { + if (err) { + (*err) += "Material index too large.\n"; + } + return false; + } + rmaterial_id = int64_t(mat_id); + + visitorEnv->converter->materialMap.add( + bound_material_path.full_path_name(), uint64_t(rmaterial_id)); + DCOUT("Added renderMaterial: " << mat_id << " " << rmat.abs_path + << " ( " << rmat.name << " ) "); + + rmaterials.push_back(rmat); + } + + return true; + }; + + // Convert bound materials in GeomSubsets + // + // key: subset Prim name + std::map subset_material_path_map; + std::vector material_subsets; + { + material_subsets = GetMaterialBindGeomSubsets(prim); + + for (const auto &psubset : material_subsets) { + MaterialPath mpath; + mpath.default_texcoords_primvar_name = + visitorEnv->env->mesh_config.default_texcoords_primvar_name; + + Path subset_abs_path = abs_path.AppendElement(psubset->name); + + // front and back + { + tinyusdz::Path bound_material_path; + const tinyusdz::Material *bound_material{nullptr}; + bool ret = tinyusdz::tydra::GetBoundMaterial( + visitorEnv->env->stage, + /* GeomSubset prim path */ subset_abs_path, + /* purpose */ "", &bound_material_path, &bound_material, err); + + if (ret && bound_material) { + int64_t rmaterial_id = -1; // not used. + + if (!ConvertBoundMaterial(bound_material_path, bound_material, + rmaterial_id)) { + if (err) { + (*err) += "Convert boundMaterial failed: " + bound_material_path.full_path_name(); + } + return false; + } + + mpath.material_path = bound_material_path.full_path_name(); + DCOUT("GeomSubset " << subset_abs_path << " : Bound material path: " + << mpath.backface_material_path); + } + } + + std::string backface_purpose = + visitorEnv->env->material_config + .default_backface_material_purpose_name; + + if (!backface_purpose.empty() && + psubset->has_materialBinding(value::token(backface_purpose))) { + DCOUT("backface_material_purpose " + << visitorEnv->env->material_config + .default_backface_material_purpose_name); + tinyusdz::Path bound_material_path; + const tinyusdz::Material *bound_material{nullptr}; + bool ret = tinyusdz::tydra::GetBoundMaterial( + visitorEnv->env->stage, + /* GeomSubset prim path */ subset_abs_path, + /* purpose */ + visitorEnv->env->material_config + .default_backface_material_purpose_name, + &bound_material_path, &bound_material, err); + + if (ret && bound_material) { + int64_t rmaterial_id = -1; // not used + + if (!ConvertBoundMaterial(bound_material_path, bound_material, + rmaterial_id)) { + if (err) { + (*err) += "Convert boundMaterial failed: " + bound_material_path.full_path_name(); + } + return false; + } + + mpath.backface_material_path = bound_material_path.full_path_name(); + DCOUT("GeomSubset " << subset_abs_path + << " : Bound backface material path: " + << mpath.backface_material_path); + } + } + + subset_material_path_map[psubset->name] = mpath; + } + } + + MaterialPath material_path; + material_path.default_texcoords_primvar_name = + visitorEnv->env->mesh_config.default_texcoords_primvar_name; + // TODO: Implement feature to assign default material + // id(MaterialPath::default_material_id) when no bound material found. + + { + const std::string mesh_path_str = abs_path.full_path_name(); + + // Front and back material. + { + tinyusdz::Path bound_material_path; + const tinyusdz::Material *bound_material{nullptr}; + bool ret = tinyusdz::tydra::GetBoundMaterial( + visitorEnv->env->stage, /* GeomMesh prim path */ abs_path, + /* purpose */ "", &bound_material_path, &bound_material, err); + + if (ret && bound_material) { + int64_t rmaterial_id = -1; // not used + + if (!ConvertBoundMaterial(bound_material_path, bound_material, + rmaterial_id)) { + if (err) { + (*err) += "Convert boundMaterial failed: " + bound_material_path.full_path_name(); + } + return false; + } + + material_path.material_path = bound_material_path.full_path_name(); + DCOUT("Bound material path: " << material_path.material_path); + } + } + + std::string backface_purpose = + visitorEnv->env->material_config + .default_backface_material_purpose_name; + + if (!backface_purpose.empty() && + pmesh->has_materialBinding(value::token(backface_purpose))) { + tinyusdz::Path bound_material_path; + const tinyusdz::Material *bound_material{nullptr}; + bool ret = tinyusdz::tydra::GetBoundMaterial( + visitorEnv->env->stage, /* GeomMesh prim path */ abs_path, + /* purpose */ + visitorEnv->env->material_config + .default_backface_material_purpose_name, + &bound_material_path, &bound_material, err); + + if (ret && bound_material) { + int64_t rmaterial_id = -1; // not used + + if (!ConvertBoundMaterial(bound_material_path, bound_material, + rmaterial_id)) { + if (err) { + (*err) += "Convert boundMaterial failed: " + bound_material_path.full_path_name(); + } + return false; + } + + material_path.backface_material_path = + bound_material_path.full_path_name(); + DCOUT("Bound backface material path: " + << material_path.backface_material_path); + } + } + + // BlendShapes + std::vector> blendshapes; + { + std::string local_err; + blendshapes = GetBlendShapes(visitorEnv->env->stage, prim, &local_err); + if (local_err.size()) { + if (err) { + (*err) += fmt::format("Failed to get BlendShapes prims. err = {}", local_err); + } + } + } + DCOUT("# of blendshapes : " << blendshapes.size()); + + RenderMesh rmesh; + + if (!visitorEnv->converter->ConvertMesh( + *visitorEnv->env, abs_path, *pmesh, material_path, + subset_material_path_map, visitorEnv->converter->materialMap, + material_subsets, blendshapes, &rmesh)) { + if (err) { + (*err) += fmt::format("Mesh conversion failed: {}", + abs_path.full_path_name()); + (*err) += "\n" + visitorEnv->converter->GetError() + "\n"; + + } + return false; + } + + uint64_t mesh_id = uint64_t(visitorEnv->converter->meshes.size()); + if (mesh_id >= (std::numeric_limits::max)()) { + if (err) { + (*err) += "Mesh index too large.\n"; + } + return false; + } + visitorEnv->converter->meshMap.add(abs_path.full_path_name(), mesh_id); + + visitorEnv->converter->meshes.emplace_back(std::move(rmesh)); + } + } + + return true; // continue traversal +} + +} // namespace + +bool RenderSceneConverter::ConvertSkelAnimation(const RenderSceneConverterEnv &env, + const Path &abs_path, + const SkelAnimation &skelAnim, + Animation *anim_out) { + // The spec says + // """ + // An animation source is only valid if its translation, rotation, and scale components are all authored, storing arrays size to the same size as the authored joints array. + // """ + + // NOTE: fortunately USD SkelAnimation uses quaternions for rotations + // anim_out->channels.rotations + + (void)anim_out; + + AnimationChannel channel_txs; channel_txs.type = AnimationChannel::ChannelType::Translation; + AnimationChannel channel_rots; channel_rots.type = AnimationChannel::ChannelType::Rotation; + AnimationChannel channel_scales; channel_scales.type = AnimationChannel::ChannelType::Scale; + + if (!skelAnim.joints.authored()) { + PUSH_ERROR_AND_RETURN(fmt::format("`joints` is not authored for SkelAnimation Prim : {}", abs_path)); + } + + std::vector joints; + if (!EvaluateTypedAttribute(env.stage, skelAnim.joints, "joints", &joints, &_err)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to evaluate `joints` in SkelAnimation Prim : {}", abs_path)); + } + + + if (!skelAnim.rotations.authored() || + !skelAnim.translations.authored() || + !skelAnim.scales.authored()) { + + PUSH_ERROR_AND_RETURN(fmt::format("`translations`, `rotations` and `scales` must be all authored for SkelAnimation Prim {}. authored flags: translations {}, rotations {}, scales {}", abs_path, skelAnim.translations.authored() ? "yes" : "no", + skelAnim.rotations.authored() ? "yes" : "no", + skelAnim.scales.authored() ? "yes" : "no")); + } + + + Animatable> translations; + if (!skelAnim.translations.get_value(&translations)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `translations` attribute of SkelAnimation. Maybe ValueBlock or connection? : {}", abs_path)); + } + + Animatable> rotations; + if (!skelAnim.rotations.get_value(&rotations)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `rotations` attribute of SkelAnimation. Maybe ValueBlock or connection? : {}", abs_path)); + } + + Animatable> scales; + if (!skelAnim.scales.get_value(&scales)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `scales` attribute of SkelAnimation. Maybe ValueBlock or connection? : {}", abs_path)); + } + + bool is_translations_timesamples = false; + bool is_rotations_timesamples = false; + bool is_scales_timesamples = false; + + if (translations.is_timesamples()) { + const TypedTimeSamples> &ts_txs = translations.get_timesamples(); + + if (ts_txs.get_samples().empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("`translations` timeSamples in SkelAnimation is empty : {}", abs_path)); + } + + for (const auto &sample : ts_txs.get_samples()) { + AnimationSample> dst; + if (!sample.blocked) { + // length check + if (sample.value.size() != joints.size()) { + PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. timeCode {} translations.size {} must be equal to joints.size {} : {}", sample.t, sample.value.size(), joints.size(), abs_path)); + } + + dst.t = float(sample.t); + dst.value = sample.value; + channel_txs.translations.samples.push_back(dst); + } + } + is_translations_timesamples = true; + } + + const TypedTimeSamples> &ts_rots = rotations.get_timesamples(); + for (const auto &sample : ts_rots.get_samples()) { + if (!sample.blocked) { + if (sample.value.size() != joints.size()) { + PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. timeCode {} rotations.size {} must be equal to joints.size {} : {}", sample.t, sample.value.size(), joints.size(), abs_path)); + } + } + } + + const TypedTimeSamples> &ts_scales = scales.get_timesamples(); + for (const auto &sample : ts_scales.get_samples()) { + if (!sample.blocked) { + if (sample.value.size() != joints.size()) { + PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. timeCode {} scales.size {} must be equal to joints.size {} : {}", sample.t, sample.value.size(), joints.size(), abs_path)); + } + } + } + + std::vector translation; + std::vector rotation; + std::vector scale; + + // Get value and also do length check for scalar(non timeSampled) animation value. + if (translations.is_scalar()) { + if (!translations.get_scalar(&translation)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `translations` attribute in SkelAnimation: {}", abs_path)); + } + if (translation.size() != joints.size()) { + PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. translations.default.size {} must be equal to joints.size {} : {}", translation.size(), joints.size(), abs_path)); + } + is_translations_timesamples = false; + } + + if (rotations.is_scalar()) { + std::vector _rotation; + if (!rotations.get_scalar(&_rotation)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `rotations` attribute in SkelAnimation: {}", abs_path)); + } + if (_rotation.size() != joints.size()) { + PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. rotations.default.size {} must be equal to joints.size {} : {}", _rotation.size(), joints.size(), abs_path)); + } + std::transform(_rotation.begin(), _rotation.end(), std::back_inserter(rotation), [](const value::quatf &v) { + value::float4 ret; + // pxrUSD's TfQuat also uses xyzw memory order. + ret[0] = v[0]; + ret[1] = v[1]; + ret[2] = v[2]; + ret[3] = v[3]; + return ret; + }); + is_rotations_timesamples = false; + } + + if (scales.is_scalar()) { + std::vector _scale; + if (!scales.get_scalar(&_scale)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to get `scales` attribute in SkelAnimation: {}", abs_path)); + } + if (_scale.size() != joints.size()) { + PUSH_ERROR_AND_RETURN(fmt::format("Array length mismatch in SkelAnimation. scale.default.size {} must be equal to joints.size {} : {}", _scale.size(), joints.size(), abs_path)); + } + // half -> float + std::transform(_scale.begin(), _scale.end(), std::back_inserter(scale), [](const value::half3 &v) { + value::float3 ret; + ret[0] = value::half_to_float(v[0]); + ret[1] = value::half_to_float(v[1]); + ret[2] = value::half_to_float(v[2]); + return ret; + }); + is_scales_timesamples = false; + } + + // Use USD TimeCode::Default for static sample. + if (is_translations_timesamples) { + } else { + AnimationSample> sample; + sample.t = std::numeric_limits::quiet_NaN(); + sample.value = translation; + channel_txs.translations.samples.push_back(sample); + } + + if (is_rotations_timesamples) { + } else { + AnimationSample> sample; + sample.t = std::numeric_limits::quiet_NaN(); + sample.value = rotation; + channel_rots.rotations.samples.push_back(sample); + } + + if (is_scales_timesamples) { + } else { + AnimationSample> sample; + sample.t = std::numeric_limits::quiet_NaN(); + sample.value = scale; + channel_scales.scales.samples.push_back(sample); + } + + PUSH_ERROR_AND_RETURN("TODO"); + +} + +bool RenderSceneConverter::BuildNodeHierarchyImpl( + const RenderSceneConverterEnv &env, const std::string &parentPrimPath, + const XformNode &node, Node &out_rnode) { + Node rnode; + + std::string primPath; + if (parentPrimPath.empty()) { + primPath = "/" + node.element_name; + } else { + primPath = parentPrimPath + "/" + node.element_name; + } + + const tinyusdz::Prim *prim = node.prim; + if (prim) { + rnode.prim_name = prim->element_name(); + rnode.abs_path = primPath; + rnode.display_name = prim->metas().displayName.value_or(""); + + DCOUT("rnode.prim_name " << rnode.prim_name); + + if (prim->type_id() == value::TYPE_ID_GEOM_MESH) { + // GeomMesh(GPrim) also has xform. + rnode.local_matrix = node.get_local_matrix(); + rnode.nodeType = NodeType::Mesh; + rnode.has_resetXform = node.has_resetXformStack(); + + if (meshMap.count(primPath)) { + rnode.id = int32_t(meshMap.at(primPath)); + } else { + rnode.id = -1; + } + } else if (prim->type_id() == value::TYPE_ID_GEOM_CAMERA) { + rnode.local_matrix = node.get_local_matrix(); + rnode.nodeType = NodeType::Mesh; + rnode.has_resetXform = node.has_resetXformStack(); + rnode.nodeType = NodeType::Camera; + rnode.id = -1; // TODO: Assign index to cameras + } else if (prim->prim_id() == value::TYPE_ID_GEOM_XFORM) { + rnode.local_matrix = node.get_local_matrix(); + rnode.global_matrix = node.get_world_matrix(); + rnode.has_resetXform = node.has_resetXformStack(); + rnode.nodeType = NodeType::Xform; + } else if (prim->prim_id() == value::TYPE_ID_SCOPE) { + // NOTE: get_local_matrix() should return identity matrix. + rnode.local_matrix = node.get_local_matrix(); + rnode.global_matrix = node.get_world_matrix(); + rnode.has_resetXform = node.has_resetXformStack(); + rnode.nodeType = NodeType::Xform; + } else if (prim->prim_id() == value::TYPE_ID_MODEL) { + rnode.local_matrix = node.get_local_matrix(); + rnode.global_matrix = node.get_world_matrix(); + rnode.has_resetXform = node.has_resetXformStack(); + rnode.nodeType = NodeType::Xform; + } else if (IsLightPrim(*prim)) { + rnode.local_matrix = node.get_local_matrix(); + rnode.global_matrix = node.get_world_matrix(); + rnode.has_resetXform = node.has_resetXformStack(); + if (prim->prim_id() == value::TYPE_ID_LUX_DISTANT) { + rnode.nodeType = NodeType::DirectionalLight; + } else if (prim->prim_id() == value::TYPE_ID_LUX_SPHERE) { + // treat sphereLight as pointLight + rnode.nodeType = NodeType::PointLight; + } else { + // TODO + rnode.nodeType = NodeType::Xform; + } + rnode.id = -1; // TODO: index to lights + } else { + // ignore other node types. + } + } + + for (const auto &child : node.children) { + Node child_rnode; + if (!BuildNodeHierarchyImpl(env, primPath, child, child_rnode)) { + return false; + } + + rnode.children.emplace_back(std::move(child_rnode)); + } + + out_rnode = std::move(rnode); + + return true; +} + +bool RenderSceneConverter::BuildNodeHierarchy( + const RenderSceneConverterEnv &env, const XformNode &root) { + std::string defaultRootNode = env.stage.metas().defaultPrim.str(); + + default_node = -1; + + for (const auto &rootNode : root.children) { + Node root_node; + if (!BuildNodeHierarchyImpl(env, /* root */ "", rootNode, root_node)) { + return false; + } + + if (defaultRootNode == rootNode.element_name) { + default_node = int(root_nodes.size()); + } + + root_nodeMap.add("/" + rootNode.element_name, root_nodes.size()); + root_nodes.push_back(root_node); + } + + return true; +} + +bool RenderSceneConverter::ConvertToRenderScene( + const RenderSceneConverterEnv &env, RenderScene *scene) { + if (!scene) { + PUSH_ERROR_AND_RETURN("nullptr for RenderScene argument."); + } + + // 1. Convert Xform + // 2. Convert Material/Texture + // 3. Convert Mesh/SkinWeights/BlendShapes + // 4. Convert Skeleton(bones) + // 5. Build node hierarchy + // TODO: Convert lights + + // + // 1. Build Xform at specified time. + // Each Prim in Stage is converted to XformNode. + // + XformNode xform_node; + if (!BuildXformNodeFromStage(env.stage, &xform_node, env.timecode)) { + PUSH_ERROR_AND_RETURN("Failed to build Xform node hierarchy.\n"); + } + + std::string err; + + // + // 2. Convert Material/Texture + // 3. Convert Mesh/SkinWeights/BlendShapes + // + // Material conversion will be done in MeshVisitor. + // + MeshVisitorEnv menv; + menv.env = &env; + menv.converter = this; + + bool ret = tydra::VisitPrims(env.stage, MeshVisitor, &menv, &err); + + if (!ret) { + PUSH_ERROR_AND_RETURN(err); + } + + // + // 4. Convert Skeletons + // + // TODO + + // + // 5. Build node hierarchy from XformNode and meshes, materials, skeletons, + // etc. + // + if (!BuildNodeHierarchy(env, xform_node)) { + return false; + } + + // render_scene.meshMap = std::move(meshMap); + // render_scene.materialMap = std::move(materialMap); + // render_scene.textureMap = std::move(textureMap); + // render_scene.imageMap = std::move(imageMap); + // render_scene.bufferMap = std::move(bufferMap); + + RenderScene render_scene; + render_scene.usd_filename = env.usd_filename; + render_scene.default_root_node = 0; + if (default_node > -1) { + if (size_t(default_node) >= root_nodes.size()) { + PushWarn("Invalid default_node id. Use 0 for default_node id."); + } else { + render_scene.default_root_node = uint32_t(default_node); + } + } + render_scene.nodes = std::move(root_nodes); + render_scene.meshes = std::move(meshes); + render_scene.textures = std::move(textures); + render_scene.images = std::move(images); + render_scene.buffers = std::move(buffers); + render_scene.materials = std::move(materials); + render_scene.skeletons = std::move(skeletons); + + (*scene) = std::move(render_scene); + return true; +} + +bool DefaultTextureImageLoaderFunction( + const value::AssetPath &assetPath, const AssetInfo &assetInfo, + const AssetResolutionResolver &assetResolver, TextureImage *texImageOut, + std::vector *imageData, void *userdata, std::string *warn, + std::string *err) { + if (!texImageOut) { + if (err) { + (*err) = "`imageOut` argument is nullptr\n"; + } + return false; + } + + if (!imageData) { + if (err) { + (*err) = "`imageData` argument is nullptr\n"; + } + return false; + } + + // TODO: assetInfo + (void)assetInfo; + (void)userdata; + (void)warn; + + std::string resolvedPath = assetResolver.resolve(assetPath.GetAssetPath()); + + if (resolvedPath.empty()) { + if (err) { + (*err) += fmt::format("Failed to resolve asset path: {}\n", + assetPath.GetAssetPath()); + } + return false; + } + + Asset asset; + bool ret = assetResolver.open_asset(resolvedPath, assetPath.GetAssetPath(), + &asset, warn, err); + if (!ret) { + if (err) { + (*err) += fmt::format("Failed to open asset: {}", resolvedPath); + } + return false; + } + + DCOUT("Resolved asset path = " << resolvedPath); + + // TODO: user-defined image loader handler. + auto result = tinyusdz::image::LoadImageFromMemory(asset.data(), asset.size(), + resolvedPath); + if (!result) { + if (err) { + (*err) += "Failed to load image file: " + result.error() + "\n"; + } + return false; + } + + TextureImage texImage; + + texImage.asset_identifier = resolvedPath; + texImage.channels = result.value().image.channels; + + const auto &imgret = result.value(); + + if (imgret.image.bpp == 8) { + // assume uint8 + texImage.assetTexelComponentType = ComponentType::UInt8; + } else if (imgret.image.bpp == 16) { + if (imgret.image.format == Image::PixelFormat::UInt) { + texImage.assetTexelComponentType = ComponentType::UInt16; + } else if (imgret.image.format == Image::PixelFormat::Int) { + texImage.assetTexelComponentType = ComponentType::Int16; + } else if (imgret.image.format == Image::PixelFormat::Float) { + texImage.assetTexelComponentType = ComponentType::Half; + } else { + if (err) { + (*err) += "Invalid image.pixelformat: " + tinyusdz::to_string(imgret.image.format) + "\n"; + } + return false; + } + + } else if (imgret.image.bpp == 16) { + if (imgret.image.format == Image::PixelFormat::UInt) { + texImage.assetTexelComponentType = ComponentType::UInt32; + } else if (imgret.image.format == Image::PixelFormat::Int) { + texImage.assetTexelComponentType = ComponentType::Int32; + } else if (imgret.image.format == Image::PixelFormat::Float) { + texImage.assetTexelComponentType = ComponentType::Float; + } else { + if (err) { + (*err) += "Invalid image.pixelformat: " + tinyusdz::to_string(imgret.image.format) + "\n"; + } + return false; + } + } else { + DCOUT("TODO: bpp = " << result.value().image.bpp); + if (err) { + (*err) += "TODO or unsupported bpp: " + + std::to_string(result.value().image.bpp) + "\n"; + } + return false; + } + + texImage.channels = result.value().image.channels; + texImage.width = result.value().image.width; + texImage.height = result.value().image.height; + + (*texImageOut) = texImage; + + // raw image data + (*imageData) = result.value().image.data; + + return true; +} + +std::string to_string(ColorSpace cty) { + std::string s; + switch (cty) { + case ColorSpace::sRGB: { + s = "srgb"; + break; + } + case ColorSpace::Linear: { + s = "linear"; + break; + } + case ColorSpace::Rec709: { + s = "rec709"; + break; + } + case ColorSpace::OCIO: { + s = "ocio"; + break; + } + case ColorSpace::Lin_ACEScg: { + s = "lin_acescg"; + break; + } + case ColorSpace::Lin_DisplayP3: { + s = "lin_displayp3"; + break; + } + case ColorSpace::sRGB_DisplayP3: { + s = "srgb_displayp3"; + break; + } + case ColorSpace::Custom: { + s = "custom"; + break; + } + } + + return s; +} + +bool InferColorSpace(const value::token &tok, ColorSpace *cty) { + if (!cty) { + return false; + } + + if (tok.str() == "raw") { + (*cty) = ColorSpace::Linear; + } else if (tok.str() == "Raw") { + (*cty) = ColorSpace::Linear; + } else if (tok.str() == "srgb") { + (*cty) = ColorSpace::sRGB; + } else if (tok.str() == "sRGB") { + (*cty) = ColorSpace::sRGB; + } else if (tok.str() == "linear") { + (*cty) = ColorSpace::Linear; + } else if (tok.str() == "rec709") { + (*cty) = ColorSpace::Rec709; + } else if (tok.str() == "ocio") { + (*cty) = ColorSpace::OCIO; + } else if (tok.str() == "lin_displayp3") { + (*cty) = ColorSpace::Lin_DisplayP3; + } else if (tok.str() == "srgb_displayp3") { + (*cty) = ColorSpace::sRGB_DisplayP3; + + // + // seen in Apple's USDZ model + // + + } else if (tok.str() == "ACES - ACEScg") { + (*cty) = ColorSpace::Lin_ACEScg; + } else if (tok.str() == "Input - Texture - sRGB - Display P3") { + (*cty) = ColorSpace::sRGB_DisplayP3; + } else if (tok.str() == "Input - Texture - sRGB - sRGB") { + (*cty) = ColorSpace::sRGB; + } else if (tok.str() == "custom") { + (*cty) = ColorSpace::Custom; + } else { + return false; + } + + return true; +} + +std::string to_string(ComponentType cty) { + std::string s; + switch (cty) { + case ComponentType::UInt8: { + s = "uint8"; + break; + } + case ComponentType::Int8: { + s = "int8"; + break; + } + case ComponentType::UInt16: { + s = "uint16"; + break; + } + case ComponentType::Int16: { + s = "int16"; + break; + } + case ComponentType::UInt32: { + s = "uint32"; + break; + } + case ComponentType::Int32: { + s = "int32"; + break; + } + case ComponentType::Half: { + s = "half"; + break; + } + case ComponentType::Float: { + s = "float"; + break; + } + case ComponentType::Double: { + s = "double"; + break; + } + } + + return s; +} + +std::string to_string(UVTexture::WrapMode mode) { + std::string s; + switch (mode) { + case UVTexture::WrapMode::REPEAT: { + s = "repeat"; + break; + } + case UVTexture::WrapMode::CLAMP_TO_BORDER: { + s = "clamp_to_border"; + break; + } + case UVTexture::WrapMode::CLAMP_TO_EDGE: { + s = "clamp_to_edge"; + break; + } + case UVTexture::WrapMode::MIRROR: { + s = "mirror"; + break; + } + } + + return s; +} + +std::string to_string(VertexVariability v) { + std::string s; + + switch (v) { + case VertexVariability::Constant: { + s = "constant"; + break; + } + case VertexVariability::Uniform: { + s = "uniform"; + break; + } + case VertexVariability::Varying: { + s = "varying"; + break; + } + case VertexVariability::Vertex: { + s = "vertex"; + break; + } + case VertexVariability::FaceVarying: { + s = "facevarying"; + break; + } + case VertexVariability::Indexed: { + s = "indexed"; + break; + } + } + + return s; +} + +std::string to_string(VertexAttributeFormat f) { + std::string s; + + switch (f) { + case VertexAttributeFormat::Bool: { + s = "bool"; + break; + } + case VertexAttributeFormat::Char: { + s = "int8"; + break; + } + case VertexAttributeFormat::Char2: { + s = "int8x2"; + break; + } + case VertexAttributeFormat::Char3: { + s = "int8x3"; + break; + } + case VertexAttributeFormat::Char4: { + s = "int8x4"; + break; + } + case VertexAttributeFormat::Byte: { + s = "uint8"; + break; + } + case VertexAttributeFormat::Byte2: { + s = "uint8x2"; + break; + } + case VertexAttributeFormat::Byte3: { + s = "uint8x3"; + break; + } + case VertexAttributeFormat::Byte4: { + s = "uint8x4"; + break; + } + case VertexAttributeFormat::Short: { + s = "int16"; + break; + } + case VertexAttributeFormat::Short2: { + s = "int16x2"; + break; + } + case VertexAttributeFormat::Short3: { + s = "int16x2"; + break; + } + case VertexAttributeFormat::Short4: { + s = "int16x2"; + break; + } + case VertexAttributeFormat::Ushort: { + s = "uint16"; + break; + } + case VertexAttributeFormat::Ushort2: { + s = "uint16x2"; + break; + } + case VertexAttributeFormat::Ushort3: { + s = "uint16x2"; + break; + } + case VertexAttributeFormat::Ushort4: { + s = "uint16x2"; + break; + } + case VertexAttributeFormat::Half: { + s = "half"; + break; + } + case VertexAttributeFormat::Half2: { + s = "half2"; + break; + } + case VertexAttributeFormat::Half3: { + s = "half3"; + break; + } + case VertexAttributeFormat::Half4: { + s = "half4"; + break; + } + case VertexAttributeFormat::Float: { + s = "float"; + break; + } + case VertexAttributeFormat::Vec2: { + s = "float2"; + break; + } + case VertexAttributeFormat::Vec3: { + s = "float3"; + break; + } + case VertexAttributeFormat::Vec4: { + s = "float4"; + break; + } + case VertexAttributeFormat::Int: { + s = "int"; + break; + } + case VertexAttributeFormat::Ivec2: { + s = "int2"; + break; + } + case VertexAttributeFormat::Ivec3: { + s = "int3"; + break; + } + case VertexAttributeFormat::Ivec4: { + s = "int4"; + break; + } + case VertexAttributeFormat::Uint: { + s = "uint"; + break; + } + case VertexAttributeFormat::Uvec2: { + s = "uint2"; + break; + } + case VertexAttributeFormat::Uvec3: { + s = "uint3"; + break; + } + case VertexAttributeFormat::Uvec4: { + s = "uint4"; + break; + } + case VertexAttributeFormat::Double: { + s = "double"; + break; + } + case VertexAttributeFormat::Dvec2: { + s = "double2"; + break; + } + case VertexAttributeFormat::Dvec3: { + s = "double3"; + break; + } + case VertexAttributeFormat::Dvec4: { + s = "double4"; + break; + } + case VertexAttributeFormat::Mat2: { + s = "mat2"; + break; + } + case VertexAttributeFormat::Mat3: { + s = "mat3"; + break; + } + case VertexAttributeFormat::Mat4: { + s = "mat4"; + break; + } + case VertexAttributeFormat::Dmat2: { + s = "dmat2"; + break; + } + case VertexAttributeFormat::Dmat3: { + s = "dmat3"; + break; + } + case VertexAttributeFormat::Dmat4: { + s = "dmat4"; + break; + } + } + + return s; +} + +namespace { + +template +std::string DumpVertexAttributeDataImpl(const T *data, const size_t nbytes, + const size_t stride_bytes, + uint32_t indent) { + size_t itemsize; + + if (stride_bytes != 0) { + if ((nbytes % stride_bytes) != 0) { + return fmt::format( + "[Invalid VertexAttributeData. input bytes {} must be dividable by " + "stride_bytes {}(Type {})]", + nbytes, stride_bytes, value::TypeTraits::type_name()); + } + itemsize = stride_bytes; + } else { + if ((nbytes % sizeof(T)) != 0) { + return fmt::format( + "[Invalid VertexAttributeData. input bytes {} must be dividable by " + "size {}(Type {})]", + nbytes, sizeof(T), value::TypeTraits::type_name()); + } + itemsize = sizeof(T); + } + + size_t nitems = nbytes / itemsize; + std::string s; + s += pprint::Indent(indent); + s += value::print_strided_array_snipped( + reinterpret_cast(data), stride_bytes, nitems); + return s; +} + +std::string DumpVertexAttributeData(const VertexAttribute &vattr, + uint32_t indent) { + // Ignore elementSize +#define APPLY_FUNC(__fmt, __basety) \ + if (__fmt == vattr.format) { \ + return DumpVertexAttributeDataImpl( \ + reinterpret_cast(vattr.data.data()), \ + vattr.data.size(), vattr.stride, indent); \ + } + + APPLY_FUNC(VertexAttributeFormat::Bool, uint8_t) + APPLY_FUNC(VertexAttributeFormat::Char, char) + APPLY_FUNC(VertexAttributeFormat::Char2, value::char2) + APPLY_FUNC(VertexAttributeFormat::Char3, value::char3) + APPLY_FUNC(VertexAttributeFormat::Char4, value::char4) + APPLY_FUNC(VertexAttributeFormat::Byte, uint8_t) + APPLY_FUNC(VertexAttributeFormat::Byte2, value::uchar2) + APPLY_FUNC(VertexAttributeFormat::Byte3, value::uchar3) + APPLY_FUNC(VertexAttributeFormat::Byte4, value::uchar4) + APPLY_FUNC(VertexAttributeFormat::Short, int16_t) + APPLY_FUNC(VertexAttributeFormat::Short2, value::short2) + APPLY_FUNC(VertexAttributeFormat::Short3, value::short3) + APPLY_FUNC(VertexAttributeFormat::Short4, value::short4) + APPLY_FUNC(VertexAttributeFormat::Ushort, uint16_t) + APPLY_FUNC(VertexAttributeFormat::Ushort2, value::ushort2) + APPLY_FUNC(VertexAttributeFormat::Ushort3, value::ushort3) + APPLY_FUNC(VertexAttributeFormat::Ushort4, value::ushort4) + APPLY_FUNC(VertexAttributeFormat::Half, value::half) + APPLY_FUNC(VertexAttributeFormat::Half2, value::half2) + APPLY_FUNC(VertexAttributeFormat::Half3, value::half3) + APPLY_FUNC(VertexAttributeFormat::Half4, value::half4) + APPLY_FUNC(VertexAttributeFormat::Float, float) + APPLY_FUNC(VertexAttributeFormat::Vec2, value::float2) + APPLY_FUNC(VertexAttributeFormat::Vec3, value::float3) + APPLY_FUNC(VertexAttributeFormat::Vec4, value::float4) + APPLY_FUNC(VertexAttributeFormat::Int, int) + APPLY_FUNC(VertexAttributeFormat::Ivec2, value::int2) + APPLY_FUNC(VertexAttributeFormat::Ivec3, value::int3) + APPLY_FUNC(VertexAttributeFormat::Ivec4, value::int4) + APPLY_FUNC(VertexAttributeFormat::Uint, uint32_t) + APPLY_FUNC(VertexAttributeFormat::Uvec2, value::half) + APPLY_FUNC(VertexAttributeFormat::Uvec3, value::half) + APPLY_FUNC(VertexAttributeFormat::Uvec4, value::half) + APPLY_FUNC(VertexAttributeFormat::Double, double) + APPLY_FUNC(VertexAttributeFormat::Dvec2, value::double2) + APPLY_FUNC(VertexAttributeFormat::Dvec3, value::double2) + APPLY_FUNC(VertexAttributeFormat::Dvec4, value::double2) + APPLY_FUNC(VertexAttributeFormat::Mat2, value::matrix2f) + APPLY_FUNC(VertexAttributeFormat::Mat3, value::matrix3f) + APPLY_FUNC(VertexAttributeFormat::Mat4, value::matrix4f) + APPLY_FUNC(VertexAttributeFormat::Dmat2, value::matrix2d) + APPLY_FUNC(VertexAttributeFormat::Dmat3, value::matrix3d) + APPLY_FUNC(VertexAttributeFormat::Dmat4, value::matrix4d) + else { + return fmt::format("[InternalError. Invalid VertexAttributeFormat: Id{}]", + int(vattr.format)); + } + +#undef APPLY_FUNC +} + +std::string DumpVertexAttribute(const VertexAttribute &vattr, uint32_t indent) { + std::stringstream ss; + + ss << pprint::Indent(indent) << "count " << vattr.get_data().size() << "\n"; + ss << pprint::Indent(indent) << "format " << quote(to_string(vattr.format)) + << "\n"; + ss << pprint::Indent(indent) << "variability " + << quote(to_string(vattr.variability)) << "\n"; + ss << pprint::Indent(indent) << "elementSize " << vattr.elementSize << "\n"; + ss << pprint::Indent(indent) << "value " + << quote(DumpVertexAttributeData(vattr, /* indent */ 0)) << "\n"; + if (vattr.indices.size()) { + ss << pprint::Indent(indent) << "indices " + << quote(value::print_array_snipped(vattr.indices)) << "\n"; + } + + return ss.str(); +} + +inline std::string to_string(NodeType ntype) { + if (ntype == NodeType::Xform) { + return "xform"; + } else if (ntype == NodeType::Mesh) { + return "mesh"; + } else if (ntype == NodeType::Camera) { + return "camera"; + } else if (ntype == NodeType::PointLight) { + return "pointLight"; + } else if (ntype == NodeType::DirectionalLight) { + return "directionalLight"; + } else if (ntype == NodeType::Skeleton) { + return "skeleton"; + } + return "???"; +} + +std::string DumpNode(const Node &node, uint32_t indent) { + std::stringstream ss; + + ss << pprint::Indent(indent) << "node {\n"; + + ss << pprint::Indent(indent + 1) << "type " << quote(to_string(node.nodeType)) + << "\n"; + + ss << pprint::Indent(indent + 1) << "id " << node.id << "\n"; + + ss << pprint::Indent(indent + 1) << "prim_name " << quote(node.prim_name) + << "\n"; + ss << pprint::Indent(indent + 1) << "abs_path " << quote(node.abs_path) + << "\n"; + ss << pprint::Indent(indent + 1) << "display_name " + << quote(node.display_name) << "\n"; + ss << pprint::Indent(indent + 1) << "local_matrix " + << quote(tinyusdz::to_string(node.local_matrix)) << "\n"; + + if (node.children.size()) { + ss << pprint::Indent(indent + 1) << "children {\n"; + for (const auto &child : node.children) { + ss << DumpNode(child, indent + 1); + } + ss << pprint::Indent(indent + 1) << "}\n"; + } + + ss << pprint::Indent(indent) << "}\n"; + + return ss.str(); +} + +void DumpMaterialSubset(std::stringstream &ss, const MaterialSubset &msubset, + uint32_t indent) { + ss << pprint::Indent(indent) << "material_subset {\n"; + ss << pprint::Indent(indent + 1) << "material_id " << msubset.material_id + << "\n"; + ss << pprint::Indent(indent + 1) << "indices " + << quote(value::print_array_snipped(msubset.indices())) << "\n"; + ss << pprint::Indent(indent) << "}\n"; +} + +std::string DumpMesh(const RenderMesh &mesh, uint32_t indent) { + std::stringstream ss; + + ss << pprint::Indent(indent) << "mesh {\n"; + + ss << pprint::Indent(indent + 1) << "prim_name " << quote(mesh.prim_name) + << "\n"; + ss << pprint::Indent(indent + 1) << "abs_path " << quote(mesh.abs_path) + << "\n"; + ss << pprint::Indent(indent + 1) << "display_name " + << quote(mesh.display_name) << "\n"; + ss << pprint::Indent(indent + 1) << "num_points " + << std::to_string(mesh.points.size()) << "\n"; + ss << pprint::Indent(indent + 1) << "points \"" + << value::print_array_snipped(mesh.points) << "\"\n"; + ss << pprint::Indent(indent + 1) << "num_faceVertexCounts " + << std::to_string(mesh.faceVertexCounts().size()) << "\n"; + ss << pprint::Indent(indent + 1) << "faceVertexCounts \"" + << value::print_array_snipped(mesh.faceVertexCounts()) << "\"\n"; + ss << pprint::Indent(indent + 1) << "num_faceVertexIndices " + << std::to_string(mesh.faceVertexIndices().size()) << "\n"; + ss << pprint::Indent(indent + 1) << "faceVertexIndices \"" + << value::print_array_snipped(mesh.faceVertexIndices()) << "\"\n"; + ss << pprint::Indent(indent + 1) << "materialId " + << std::to_string(mesh.material_id) << "\n"; + ss << pprint::Indent(indent + 1) << "normals {\n" + << DumpVertexAttribute(mesh.normals, indent + 2) << "\n"; + ss << pprint::Indent(indent + 1) << "}\n"; + ss << pprint::Indent(indent + 1) << "num_texcoordSlots " + << std::to_string(mesh.texcoords.size()) << "\n"; + for (const auto &uvs : mesh.texcoords) { + ss << pprint::Indent(indent + 1) << "texcoords_" + << std::to_string(uvs.first) << " {\n" + << DumpVertexAttribute(uvs.second, indent + 2) << "\n"; + ss << pprint::Indent(indent + 1) << "}\n"; + } + if (mesh.binormals.data.size()) { + ss << pprint::Indent(indent + 1) << "binormals {\n" + << DumpVertexAttribute(mesh.binormals, indent + 2) << "\n"; + ss << pprint::Indent(indent + 1) << "}\n"; + } + if (mesh.tangents.data.size()) { + ss << pprint::Indent(indent + 1) << "tangents {\n" + << DumpVertexAttribute(mesh.tangents, indent + 2) << "\n"; + ss << pprint::Indent(indent + 1) << "}\n"; + } + + if (mesh.joint_and_weights.jointIndices.size()) { + ss << pprint::Indent(indent + 1) << "skin {\n"; + ss << pprint::Indent(indent + 2) << "geomBindTransform " + << quote(tinyusdz::to_string(mesh.joint_and_weights.geomBindTransform)) + << "\n"; + ss << pprint::Indent(indent + 2) << "elementSize " + << mesh.joint_and_weights.elementSize << "\n"; + ss << pprint::Indent(indent + 2) << "jointIndices " + << quote(value::print_array_snipped(mesh.joint_and_weights.jointIndices)) + << "\n"; + ss << pprint::Indent(indent + 2) << "jointWeights " + << quote(value::print_array_snipped(mesh.joint_and_weights.jointWeights)) + << "\n"; + ss << pprint::Indent(indent + 1) << "}\n"; + } + if (mesh.targets.size()) { + ss << pprint::Indent(indent + 1) << "shapeTargets {\n"; + + for (const auto &target : mesh.targets) { + ss << pprint::Indent(indent + 2) << target.first << " {\n"; + ss << pprint::Indent(indent + 3) << "prim_name " << quote(target.second.prim_name) << "\n"; + ss << pprint::Indent(indent + 3) << "abs_path " << quote(target.second.abs_path) << "\n"; + ss << pprint::Indent(indent + 3) << "display_name " << quote(target.second.display_name) << "\n"; + ss << pprint::Indent(indent + 3) << "pointIndices " << quote(value::print_array_snipped(target.second.pointIndices)) << "\n"; + ss << pprint::Indent(indent + 3) << "pointOffsets " << quote(value::print_array_snipped(target.second.pointOffsets)) << "\n"; + ss << pprint::Indent(indent + 3) << "normalOffsets " << quote(value::print_array_snipped(target.second.normalOffsets)) << "\n"; + ss << pprint::Indent(indent + 2) << "}\n"; + } + + ss << pprint::Indent(indent + 1) << "}\n"; + + } + if (mesh.material_subsetMap.size()) { + ss << pprint::Indent(indent + 1) << "material_subsets {\n"; + for (const auto &msubset : mesh.material_subsetMap) { + DumpMaterialSubset(ss, msubset.second, indent + 2); + } + ss << pprint::Indent(indent + 1) << "}\n"; + } + + // TODO: primvars + + ss << "\n"; + + ss << pprint::Indent(indent) << "}\n"; + + return ss.str(); +} + +std::string DumpCamera(const RenderCamera &camera, uint32_t indent) { + std::stringstream ss; + + ss << pprint::Indent(indent) << "camera {\n"; + + ss << pprint::Indent(indent + 1) << "name " << quote(camera.name) << "\n"; + ss << pprint::Indent(indent + 1) << "abs_path " << quote(camera.abs_path) + << "\n"; + ss << pprint::Indent(indent + 1) << "display_name " + << quote(camera.display_name) << "\n"; + ss << pprint::Indent(indent + 1) << "shutterOpen " + << std::to_string(camera.shutterOpen) << "\n"; + ss << pprint::Indent(indent + 1) << "shutterClose " + << std::to_string(camera.shutterClose) << "\n"; + + ss << "\n"; + + ss << pprint::Indent(indent) << "}\n"; + + return ss.str(); +} + +std::string DumpPreviewSurface(const PreviewSurfaceShader &shader, + uint32_t indent) { + std::stringstream ss; + + ss << "PreviewSurfaceShader {\n"; + + ss << pprint::Indent(indent + 1) + << "useSpecularWorkFlow = " << std::to_string(shader.useSpecularWorkFlow) + << "\n"; + + ss << pprint::Indent(indent + 1) << "diffuseColor = "; + if (shader.diffuseColor.is_texture()) { + ss << "textureId[" << shader.diffuseColor.textureId << "]"; + } else { + ss << shader.diffuseColor.value; + } + ss << "\n"; + + ss << pprint::Indent(indent + 1) << "metallic = "; + if (shader.metallic.is_texture()) { + ss << "textureId[" << shader.metallic.textureId << "]"; + } else { + ss << shader.metallic.value; + } + ss << "\n"; + + ss << pprint::Indent(indent + 1) << "roughness = "; + if (shader.roughness.is_texture()) { + ss << "textureId[" << shader.roughness.textureId << "]"; + } else { + ss << shader.roughness.value; + } + ss << "\n"; + + ss << pprint::Indent(indent + 1) << "ior = "; + if (shader.ior.is_texture()) { + ss << "textureId[" << shader.ior.textureId << "]"; + } else { + ss << shader.ior.value; + } + ss << "\n"; + + ss << pprint::Indent(indent + 1) << "clearcoat = "; + if (shader.clearcoat.is_texture()) { + ss << "textureId[" << shader.clearcoat.textureId << "]"; + } else { + ss << shader.clearcoat.value; + } + ss << "\n"; + + ss << pprint::Indent(indent + 1) << "clearcoatRoughness = "; + if (shader.clearcoatRoughness.is_texture()) { + ss << "textureId[" << shader.clearcoatRoughness.textureId << "]"; + } else { + ss << shader.clearcoatRoughness.value; + } + ss << "\n"; + + ss << pprint::Indent(indent + 1) << "opacity = "; + if (shader.opacity.is_texture()) { + ss << "textureId[" << shader.opacity.textureId << "]"; + } else { + ss << shader.opacity.value; + } + ss << "\n"; + + ss << pprint::Indent(indent + 1) << "opacityThreshold = "; + if (shader.opacityThreshold.is_texture()) { + ss << "textureId[" << shader.opacityThreshold.textureId << "]"; + } else { + ss << shader.opacityThreshold.value; + } + ss << "\n"; + + ss << pprint::Indent(indent + 1) << "normal = "; + if (shader.normal.is_texture()) { + ss << "textureId[" << shader.normal.textureId << "]"; + } else { + ss << shader.normal.value; + } + ss << "\n"; + + ss << pprint::Indent(indent + 1) << "displacement = "; + if (shader.displacement.is_texture()) { + ss << "textureId[" << shader.displacement.textureId << "]"; + } else { + ss << shader.displacement.value; + } + ss << "\n"; + + ss << pprint::Indent(indent + 1) << "occlusion = "; + if (shader.occlusion.is_texture()) { + ss << "textureId[" << shader.occlusion.textureId << "]"; + } else { + ss << shader.occlusion.value; + } + ss << "\n"; + + ss << pprint::Indent(indent) << "}\n"; + + return ss.str(); +} + +std::string DumpMaterial(const RenderMaterial &material, uint32_t indent) { + std::stringstream ss; + + ss << pprint::Indent(indent) << "material {\n"; + + ss << pprint::Indent(indent + 1) << "name " << quote(material.name) << "\n"; + ss << pprint::Indent(indent + 1) << "abs_path " << quote(material.abs_path) + << "\n"; + ss << pprint::Indent(indent + 1) << "display_name " + << quote(material.display_name) << "\n"; + + ss << pprint::Indent(indent + 1) << "surfaceShader = "; + ss << DumpPreviewSurface(material.surfaceShader, indent + 1); + ss << "\n"; + + ss << pprint::Indent(indent) << "}\n"; + + return ss.str(); +} + +std::string DumpUVTexture(const UVTexture &texture, uint32_t indent) { + std::stringstream ss; + + // TODO + ss << "UVTexture {\n"; + ss << pprint::Indent(indent + 1) << "primvar_name " << texture.varname_uv + << "\n"; + ss << pprint::Indent(indent + 1) << "outputChannel " + << to_string(texture.outputChannel) << "\n"; + ss << pprint::Indent(indent + 1) << "bias " << texture.bias << "\n"; + ss << pprint::Indent(indent + 1) << "scale " << texture.scale << "\n"; + ss << pprint::Indent(indent + 1) << "wrapS " << to_string(texture.wrapS) + << "\n"; + ss << pprint::Indent(indent + 1) << "wrapT " << to_string(texture.wrapT) + << "\n"; + ss << pprint::Indent(indent + 1) << "fallback_uv " << texture.fallback_uv + << "\n"; + ss << pprint::Indent(indent + 1) << "textureImageID " + << std::to_string(texture.texture_image_id) << "\n"; + ss << pprint::Indent(indent + 1) << "has UsdTransform2d " + << std::to_string(texture.has_transform2d) << "\n"; + if (texture.has_transform2d) { + ss << pprint::Indent(indent + 2) << "rotation " << texture.tx_rotation + << "\n"; + ss << pprint::Indent(indent + 2) << "scale " << texture.tx_scale << "\n"; + ss << pprint::Indent(indent + 2) << "translation " << texture.tx_translation + << "\n"; + ss << pprint::Indent(indent + 2) << "computed_transform " + << texture.transform << "\n"; + } + + ss << "\n"; + + ss << pprint::Indent(indent) << "}\n"; + + return ss.str(); +} + +std::string DumpImage(const TextureImage &image, uint32_t indent) { + std::stringstream ss; + + ss << "TextureImage {\n"; + ss << pprint::Indent(indent + 1) << "asset_identifier \"" + << image.asset_identifier << "\"\n"; + ss << pprint::Indent(indent + 1) << "channels " + << std::to_string(image.channels) << "\n"; + ss << pprint::Indent(indent + 1) << "width " << std::to_string(image.width) + << "\n"; + ss << pprint::Indent(indent + 1) << "height " << std::to_string(image.height) + << "\n"; + ss << pprint::Indent(indent + 1) << "miplevel " + << std::to_string(image.miplevel) << "\n"; + ss << pprint::Indent(indent + 1) << "colorSpace " + << to_string(image.colorSpace) << "\n"; + ss << pprint::Indent(indent + 1) << "bufferID " + << std::to_string(image.buffer_id) << "\n"; + + ss << "\n"; + + ss << pprint::Indent(indent) << "}\n"; + + return ss.str(); +} + +std::string DumpBuffer(const BufferData &buffer, uint32_t indent) { + std::stringstream ss; + + ss << "Buffer {\n"; + ss << pprint::Indent(indent + 1) << "bytes " << buffer.data.size() << "\n"; + ss << pprint::Indent(indent + 1) << "componentType " + << to_string(buffer.componentType) << "\n"; + + ss << "\n"; + + ss << pprint::Indent(indent) << "}\n"; + + return ss.str(); +} + +} // namespace + +std::string DumpRenderScene(const RenderScene &scene, + const std::string &format) { + std::stringstream ss; + + if (format == "json") { + // TODO: + // Currently kdl only. + ss << "// `json` format is not supported yet. Use KDL format\n"; + } + + ss << "title " << quote(scene.usd_filename) << "\n"; + ss << "default_root_node " << scene.default_root_node << "\n"; + ss << "// # of Root Nodes : " << scene.nodes.size() << "\n"; + ss << "// # of Meshes : " << scene.meshes.size() << "\n"; + ss << "// # of Cameras : " << scene.cameras.size() << "\n"; + ss << "// # of Animations : " << scene.animations.size() << "\n"; + ss << "// # of Materials : " << scene.materials.size() << "\n"; + ss << "// # of UVTextures : " << scene.textures.size() << "\n"; + ss << "// # of TextureImages : " << scene.images.size() << "\n"; + ss << "// # of Buffers : " << scene.buffers.size() << "\n"; + + ss << "\n"; + + ss << "nodes {\n"; + for (size_t i = 0; i < scene.nodes.size(); i++) { + ss << DumpNode(scene.nodes[i], 1); + } + ss << "}\n"; + + ss << "meshes {\n"; + for (size_t i = 0; i < scene.meshes.size(); i++) { + ss << "[" << i << "] " << DumpMesh(scene.meshes[i], 1); + } + ss << "}\n"; + + ss << "cameras {\n"; + for (size_t i = 0; i < scene.cameras.size(); i++) { + ss << "[" << i << "] " << DumpCamera(scene.cameras[i], 1); + } + ss << "}\n"; + + ss << "\n"; + ss << "materials {\n"; + for (size_t i = 0; i < scene.materials.size(); i++) { + ss << "[" << i << "] " << DumpMaterial(scene.materials[i], 1); + } + ss << "}\n"; + + ss << "\n"; + ss << "textures {\n"; + for (size_t i = 0; i < scene.textures.size(); i++) { + ss << "[" << i << "] " << DumpUVTexture(scene.textures[i], 1); + } + ss << "}\n"; + + ss << "\n"; + ss << "images {\n"; + for (size_t i = 0; i < scene.images.size(); i++) { + ss << "[" << i << "] " << DumpImage(scene.images[i], 1); + } + ss << "}\n"; + + ss << "\n"; + ss << "buffers {\n"; + for (size_t i = 0; i < scene.buffers.size(); i++) { + ss << "[" << i << "] " << DumpBuffer(scene.buffers[i], 1); + } + ss << "}\n"; + + // ss << "TODO: Animations, ...\n"; + + return ss.str(); +} + +} // namespace tydra +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/render-data.hh b/contrib/tinyusdz/tinyusdz_repo/src/tydra/render-data.hh new file mode 100644 index 000000000..a129b8296 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/render-data.hh @@ -0,0 +1,1794 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +// Render data structure suited for WebGL and Raytracing render +// +#pragma once + +#include +#include +#include + +#include "asset-resolution.hh" +#include "nonstd/expected.hpp" +#include "usdGeom.hh" +#include "usdShade.hh" +#include "usdSkel.hh" +#include "value-types.hh" + +// tydra +#include "scene-access.hh" + +namespace tinyusdz { + +// forward decl +class Stage; +class Prim; +struct Material; +struct GeomMesh; +struct Xform; +struct AssetInfo; +class Path; +struct UsdPreviewSurface; +struct UsdUVTexture; + +template +struct UsdPrimvarReader; + +using UsdPrimvarReader_int = UsdPrimvarReader; +using UsdPrimvarReader_float = UsdPrimvarReader; +using UsdPrimvarReader_float3 = UsdPrimvarReader; +using UsdPrimvarReader_float3 = UsdPrimvarReader; +using UsdPrimvarReader_string = UsdPrimvarReader; +using UsdPrimvarReader_matrix4d = UsdPrimvarReader; + +namespace tydra { + +// GLSL like data types +using vec2 = value::float2; +using vec3 = value::float3; +using vec4 = value::float4; +using quat = value::float4; // (x, y, z, w) +using mat2 = value::matrix2f; +using mat3 = value::matrix3f; +using mat4 = value::matrix4f; +using dmat4 = value::matrix4d; + +// Simple string <-> id map +struct StringAndIdMap { + void add(uint64_t key, const std::string &val) { + _i_to_s[key] = val; + _s_to_i[val] = key; + } + + void add(const std::string &key, uint64_t val) { + _s_to_i[key] = val; + _i_to_s[val] = key; + } + + bool empty() const { return _i_to_s.empty(); } + + size_t count(uint64_t i) const { return _i_to_s.count(i); } + + size_t count(const std::string &s) const { return _s_to_i.count(s); } + + std::string at(uint64_t i) const { return _i_to_s.at(i); } + + uint64_t at(std::string s) const { return _s_to_i.at(s); } + + std::map::const_iterator find(uint64_t key) const { + return _i_to_s.find(key); + } + + std::map::const_iterator find( + const std::string &key) const { + return _s_to_i.find(key); + } + + std::map::const_iterator s_begin() const { + return _s_to_i.begin(); + } + + std::map::const_iterator s_end() const { + return _s_to_i.end(); + } + + std::map::const_iterator i_begin() const { + return _i_to_s.begin(); + } + + std::map::const_iterator i_end() const { + return _i_to_s.end(); + } + + size_t size() const { + // size should be same, but just in case. + if (_i_to_s.size() == _s_to_i.size()) { + return _i_to_s.size(); + } + + return 0; + } + + std::map _i_to_s; // index -> string + std::map _s_to_i; // string -> index +}; + +// timeSamples in USD +// TODO: AttributeBlock support +template +struct AnimationSample { + float t{0.0}; // time is represented as float + T value; +}; + +enum class VertexVariability { + Constant, // one value for all geometric elements + Uniform, // one value for each geometric elements(e.g. `face`, `UV patch`) + Varying, // per-vertex for each geometric elements. Bilinear interpolation. + Vertex, // Equvalent to `Varying` for Polygon mesh. The basis function of the + // surface is used for the interpolation(Curves, Subdivision Surface, + // etc). + FaceVarying, // per-Vertex per face. Bilinear interpolation. + Indexed, // Dedicated index buffer provided(unflattened Indexed Primvar). +}; + +std::string to_string(VertexVariability variability); + +enum class NodeType { + Xform, + Mesh, // Polygon mesh + Camera, + Skeleton, // SkelHierarchy + PointLight, + DirectionalLight, + EnvmapLight, // DomeLight in USD + // TODO(more lights)... +}; + +enum class ComponentType { + UInt8, + Int8, + UInt16, + Int16, + UInt32, + Int32, + Half, + Float, + Double, +}; + +std::string to_string(ComponentType ty); + +// glTF-like BufferData +struct BufferData { + ComponentType componentType{ComponentType::UInt8}; + //uint8_t count{1}; // # of components. up to 256 + std::vector data; // binary data. size is dividable by sizeof(componentType) + + // TODO: Stride? +}; + +// Compound of ComponentType x component +enum class VertexAttributeFormat { + Bool, // bool(1 byte) + Char, // int8 + Char2, // int8x2 + Char3, // int8x3 + Char4, // int8x4 + Byte, // uint8 + Byte2, // uint8x2 + Byte3, // uint8x3 + Byte4, // uint8x4 + Short, // int16 + Short2, // int16x2 + Short3, // int16x2 + Short4, // int16x2 + Ushort, // uint16 + Ushort2, // uint16x2 + Ushort3, // uint16x2 + Ushort4, // uint16x2 + Half, // half + Half2, // half2 + Half3, // half3 + Half4, // half4 + Float, // float + Vec2, // float2 + Vec3, // float3 + Vec4, // float4 + Int, // int + Ivec2, // int2 + Ivec3, // int3 + Ivec4, // int4 + Uint, // uint + Uvec2, // uint2 + Uvec3, // uint3 + Uvec4, // uint4 + Double, // double + Dvec2, // double2 + Dvec3, // double3 + Dvec4, // double4 + Mat2, // float 2x2 + Mat3, // float 3x3 + Mat4, // float 4x4 + Dmat2, // double 2x2 + Dmat3, // double 3x3 + Dmat4, // double 4x4 +}; + +static size_t VertexAttributeFormatSize(VertexAttributeFormat f) { + size_t elemsize{0}; + + switch (f) { + case VertexAttributeFormat::Bool: { + elemsize = 1; + break; + } + case VertexAttributeFormat::Char: { + elemsize = 1; + break; + } + case VertexAttributeFormat::Char2: { + elemsize = 2; + break; + } + case VertexAttributeFormat::Char3: { + elemsize = 3; + break; + } + case VertexAttributeFormat::Char4: { + elemsize = 4; + break; + } + case VertexAttributeFormat::Byte: { + elemsize = 1; + break; + } + case VertexAttributeFormat::Byte2: { + elemsize = 2; + break; + } + case VertexAttributeFormat::Byte3: { + elemsize = 3; + break; + } + case VertexAttributeFormat::Byte4: { + elemsize = 4; + break; + } + case VertexAttributeFormat::Short: { + elemsize = 2; + break; + } + case VertexAttributeFormat::Short2: { + elemsize = 4; + break; + } + case VertexAttributeFormat::Short3: { + elemsize = 6; + break; + } + case VertexAttributeFormat::Short4: { + elemsize = 8; + break; + } + case VertexAttributeFormat::Ushort: { + elemsize = 2; + break; + } + case VertexAttributeFormat::Ushort2: { + elemsize = 4; + break; + } + case VertexAttributeFormat::Ushort3: { + elemsize = 6; + break; + } + case VertexAttributeFormat::Ushort4: { + elemsize = 8; + break; + } + case VertexAttributeFormat::Half: { + elemsize = 2; + break; + } + case VertexAttributeFormat::Half2: { + elemsize = 4; + break; + } + case VertexAttributeFormat::Half3: { + elemsize = 6; + break; + } + case VertexAttributeFormat::Half4: { + elemsize = 8; + break; + } + case VertexAttributeFormat::Mat2: { + elemsize = 4 * 4; + break; + } + case VertexAttributeFormat::Mat3: { + elemsize = 4 * 9; + break; + } + case VertexAttributeFormat::Mat4: { + elemsize = 4 * 16; + break; + } + case VertexAttributeFormat::Dmat2: { + elemsize = 8 * 4; + break; + } + case VertexAttributeFormat::Dmat3: { + elemsize = 8 * 9; + break; + } + case VertexAttributeFormat::Dmat4: { + elemsize = 8 * 16; + break; + } + case VertexAttributeFormat::Float: { + elemsize = 4; + break; + } + case VertexAttributeFormat::Vec2: { + elemsize = sizeof(float) * 2; + break; + } + case VertexAttributeFormat::Vec3: { + elemsize = sizeof(float) * 3; + break; + } + case VertexAttributeFormat::Vec4: { + elemsize = sizeof(float) * 4; + break; + } + case VertexAttributeFormat::Int: { + elemsize = 4; + break; + } + case VertexAttributeFormat::Ivec2: { + elemsize = sizeof(int) * 2; + break; + } + case VertexAttributeFormat::Ivec3: { + elemsize = sizeof(int) * 3; + break; + } + case VertexAttributeFormat::Ivec4: { + elemsize = sizeof(int) * 4; + break; + } + case VertexAttributeFormat::Uint: { + elemsize = 4; + break; + } + case VertexAttributeFormat::Uvec2: { + elemsize = sizeof(uint32_t) * 2; + break; + } + case VertexAttributeFormat::Uvec3: { + elemsize = sizeof(uint32_t) * 3; + break; + } + case VertexAttributeFormat::Uvec4: { + elemsize = sizeof(uint32_t) * 4; + break; + } + case VertexAttributeFormat::Double: { + elemsize = sizeof(double); + break; + } + case VertexAttributeFormat::Dvec2: { + elemsize = sizeof(double) * 2; + break; + } + case VertexAttributeFormat::Dvec3: { + elemsize = sizeof(double) * 3; + break; + } + case VertexAttributeFormat::Dvec4: { + elemsize = sizeof(double) * 4; + break; + } + } + + return elemsize; +} + +std::string to_string(VertexAttributeFormat f); + +/// +/// Vertex attribute array. Stores raw vertex attribute data. +/// +/// arrayLength = elementSize * vertexCount +/// arrayBytes = formatSize * elementSize * vertexCount +/// +/// Example: +/// positions(float3, elementSize=1, n=2): [1.0, 1.1, 1.2, 0.4, 0.3, 0.2] +/// skinWeights(float, elementSize=4, n=2): [1.0, 1.0, 1.0, 1.0, 0.5, 0.5, +/// 0.5, 0.5] +/// +struct VertexAttribute { + std::string name; // Attribute(primvar) name. Optional. Can be empty. + VertexAttributeFormat format{VertexAttributeFormat::Vec3}; + uint32_t elementSize{1}; // `elementSize` in USD terminology(i.e. # of + // samples per vertex data) + uint32_t stride{0}; // We don't support packed(interleaved) vertex data, so + // stride is usually sizeof(VertexAttributeFormat) * + // elementSize. 0 = tightly packed. + std::vector data; // raw binary data(TODO: Use Buffer ID?) + std::vector + indices; // Dedicated Index buffer. Set when variability == Indexed. + // empty = Use externally provided vertex index buffer + VertexVariability variability{VertexVariability::Vertex}; + uint64_t handle{0}; // Handle ID for Graphics API. 0 = invalid + + // + // Returns the number of vertex items(excludes `elementSize`). + // + // We use compound type for the format. + // For example, this returns 1 when the buffer is + // composed of 3 floats and `format` is float3(in any elementSize >= 1). + size_t vertex_count() const { + if (stride != 0) { + // TODO: return 0 when (data.size() % stride) != 0? + return data.size() / stride; + } + + size_t itemSize = stride_bytes(); + + if ((data.size() % itemSize) != 0) { + // data size mismatch + return 0; + } + + return data.size() / itemSize; + } + + inline bool empty() const { + return data.empty(); + } + + size_t num_bytes() const { return data.size(); } + + const void *buffer() const { + return reinterpret_cast(data.data()); + } + + void set_buffer(const uint8_t *addr, size_t n) { + data.resize(n); + memcpy(data.data(), addr, n); + } + + const std::vector &get_data() const { return data; } + + std::vector &get_data() { return data; } + + // + // Bytes for each vertex item. + // Returns `formatSize * elementSize` when `stride` is 0. + // Returns `stride` when `stride` is not zero. + // + size_t stride_bytes() const { + if (stride != 0) { + return stride; + } + + return element_size() * VertexAttributeFormatSize(format); + } + + size_t element_size() const { return elementSize; } + + size_t format_size() const { return VertexAttributeFormatSize(format); } + + bool is_constant() const { + return (variability == VertexVariability::Constant); + } + + bool is_uniform() const { + return (variability == VertexVariability::Constant); + } + + // includes 'varying' + bool is_vertex() const { + return (variability == VertexVariability::Vertex) || + (variability == VertexVariability::Varying); + } + + bool is_facevarying() const { + return (variability == VertexVariability::FaceVarying); + } + + bool is_indexed() const { return variability == VertexVariability::Indexed; } +}; + +#if 0 // TODO: Implement +/// +/// Flatten(expand by vertexCounts and vertexIndices) VertexAttribute. +/// +/// @param[in] src Input VertexAttribute. +/// @param[in] faceVertexCounts Array of faceVertex counts. +/// @param[in] faceVertexIndices Array of faceVertex indices. +/// @param[out] dst flattened VertexAttribute data. +/// @param[out] itemCount # of vertex items = dst.size() / src.stride_bytes(). +/// +static bool FlattenVertexAttribute( + const VertexAttribute &src, + const std::vector &faceVertexCounts, + const std::vector &faceVertexIndices, + std::vector &dst, + size_t &itemCount); +#else + +#if 0 // TODO: Implement +/// +/// Convert variability of `src` VertexAttribute to "facevarying". +/// +/// @param[in] src Input VertexAttribute. +/// @param[in] faceVertexCounts # of vertex per face. When the size is empty +/// and faceVertexIndices is not empty, treat `faceVertexIndices` as +/// triangulated mesh indices. +/// @param[in] faceVertexIndices +/// @param[out] dst VertexAttribute with facevarying variability. `dst.vertex_count()` become `sum(faceVertexCounts)` +/// +static bool ToFacevaringVertexAttribute( + const VertexAttribute &src, VertexAttribute &dst, + const std::vector &faceVertexCounts, + const std::vector &faceVertexIndices); +#endif +#endif + +// +// Convert PrimVar(type-erased value) at specified time to VertexAttribute +// +// Input Primvar's name, variability(interpolation) and elementSize are +// preserved. Use Primvar's underlying type to set the type of VertexAttribute. +// (example: 'color3f'(underlying type 'float3') -> Vec3) +// +// @param[in] pvar GeomPrimvar. +// @param[out] dst Output VertexAttribute. +// @param[out] err Error messsage. can be nullptr +// @param[in] t timecode +// @param[in] tinterp Interpolation for timesamples +// +// @return true upon success. +// +bool ToVertexAttribute(const GeomPrimvar &pvar, VertexAttribute &dst, + std::string *err, + const double t = value::TimeCode::Default(), + const value::TimeSampleInterpolationType tinterp = + value::TimeSampleInterpolationType::Linear); + +enum class ColorSpace { + sRGB, + Linear, + Rec709, + Lin_ACEScg, // ACES CG colorspace(linear colorspace. no transfer curve applied) + OCIO, + Lin_DisplayP3, // colorSpace 'lin_displayp3' + sRGB_DisplayP3, // colorSpace 'srgb_displayp3' + Custom, // TODO: Custom colorspace +}; + +std::string to_string(ColorSpace cs); + +// Infer colorspace from token value. +bool InferColorSpace(const value::token &tok, ColorSpace *result); + +struct TextureImage { + std::string asset_identifier; // (resolved) filename or asset identifier. + + ComponentType texelComponentType{ + ComponentType::UInt8}; // texel bit depth of `buffer_id` + ComponentType assetTexelComponentType{ + ComponentType::UInt8}; // texel bit depth of UsdUVTexture asset + + ColorSpace colorSpace{ColorSpace::sRGB}; // color space of texel data. + ColorSpace usdColorSpace{ + ColorSpace::sRGB}; // original color space info in UsdUVTexture + + int32_t width{-1}; + int32_t height{-1}; + int32_t channels{-1}; // e.g. 3 for RGB. + int32_t miplevel{0}; + + int64_t buffer_id{-1}; // index to buffer_id(texel data) + + uint64_t handle{0}; // Handle ID for Graphics API. 0 = invalid +}; + +struct Cubemap +{ + // face id mapping(based on OpenGL) + // https://www.khronos.org/opengl/wiki/Cubemap_Texture + // + // 0: +X (right) + // 1: -X (left) + // 2: +Y (top) + // 3: -Y (bottom) + // 4: +Z (back) + // 5: -Z (front) + + // LoD of cubemap + std::vector> faces_lod; +}; + +// Envmap lightsource +struct EnvmapLight +{ + enum class Coordinate { + LatLong, // "latlong" + Angular, // "angular" + // MirroredBall, // TODO: "mirroredBall" + Cubemap, // TinyUSDZ Tydra specific. + }; + + std::string element_name; + std::string abs_path; + std::string display_name; + + double guideRadius{1.0e5}; + std::string asset_name; // 'inputs:texture:file' + + std::vector texture_lod; + + // Utility + bool to_cubemap(Cubemap &cubemap); + +}; + +// glTF-lie animation data + +// TOOD: Implement Animation sample resampler. + +// In USD, timeSamples are linearly interpolated by default. +// For default value(value at static time), create a Sample at time `-inf`(USD TimeCode::Default) +template +struct AnimationSampler { + std::vector> samples; + + // No cubicSpline in USD + enum class Interpolation { + Linear, + Step, // Held in USD + }; + + Interpolation interpolation{Interpolation::Linear}; +}; + +// TODO: Supprot more data types(e.g. float2) +struct AnimationChannel { + enum class ChannelType { Transform, Translation, Rotation, Scale, Weight }; + + ChannelType type; + // The following AnimationSampler is filled depending on ChannelType. + // Example: Rotation => Only `rotations` are filled. + + // Matrix precision is reduced to float-precision + // NOTE: transform is not supported in glTF(you need to decompose transform + // matrix into TRS) + AnimationSampler> transforms; + + AnimationSampler> translations; + AnimationSampler> rotations; // Rotation is represented as quaternions + AnimationSampler> scales; // half-types are upcasted to float precision + AnimationSampler> weights; + + int64_t taget_node{-1}; // array index to RenderScene::nodes +}; + +// USD SkelAnimation +struct Animation { + std::string prim_name; // Prim name(element name) + std::string abs_path; // Target USD Prim path + std::string display_name; // `displayName` prim meta + std::vector channels; +}; + +struct Node { + std::string prim_name; // Prim name(element name) + std::string abs_path; // Absolute prim path + std::string display_name; // `displayName` prim meta + + NodeType nodeType{NodeType::Xform}; + + int32_t id{-1}; // Index to node content(e.g. meshes[id] when nodeTypes == + // Mesh). -1 = no corresponding content exists for this node. + + std::vector children; + + // Every node have its transform at specified timecode. + // `resetXform` is encoded in global matrix. + value::matrix4d local_matrix; + value::matrix4d global_matrix; // = local_matrix * parent_matrix (USD use + // row-major(pre-multiply)) + + bool has_resetXform{false}; // true: When updating the transform of the node, need to reset parent's matrix to compute global matrix. + + bool is_identity_matrix() { return is_identity(local_matrix); } + + std::vector + node_animations; // xform animations(timesamples) + + uint64_t handle{0}; // Handle ID for Graphics API. 0 = invalid +}; + +// BlendShape shape target. + +struct InbetweenShapeTarget { + std::vector pointOffsets; + std::vector normalOffsets; + float weight{0.5f}; // TODO: Init with invalid weight? +}; + +struct ShapeTarget { + std::string prim_name; // Prim name + std::string abs_path; // Absolute prim path + std::string display_name; // `displayName` prim meta + + std::vector pointIndices; + std::vector pointOffsets; + std::vector normalOffsets; + + // key = weight + std::unordered_map inbetweens; +}; + +struct JointAndWeight { + value::matrix4d geomBindTransform{ + value::matrix4d::identity()}; // matrix4d primvars:skel:geomBindTransform + + // + // NOTE: variability of jointIndices and jointWeights are 'vertex' + // NOTE: Values in jointIndices and jointWeights will be reordered when `MeshConverterConfig::build_vertex_indices` is set true. + // + std::vector jointIndices; // int[] primvars:skel:jointIndices + + // NOTE: weight is converted from USD as-is. not normalized. + std::vector jointWeights; // float[] primvars:skel:jointWeight; + + int elementSize{1}; // # of weights per vertex +}; + +struct MaterialPath { + std::string material_path; // USD Material Prim path. + std::string backface_material_path; // USD Material Prim path. + + // Default RenderMaterial Id to assign when + // material_path/backface_material_path is empty. -1 = no material will be + // assigned. + int default_material_id{-1}; + int default_backface_material_id{-1}; + + // primvar name used for texcoords when default RenderMaterial is used. + // Currently we don't support different texcoord for each frontface and + // backface material. + std::string default_texcoords_primvar_name{"st"}; +}; + +// GeomSubset whose familyName is 'materialBind'. +// For per-face material mapping. +struct MaterialSubset { + std::string prim_name; // Prim name in Stage + std::string abs_path; // Absolute Prim path in Stage + std::string display_name; // `displayName` Prim meta + int64_t prim_index{-1}; // Prim index in Stage + + // Index to RenderScene::materials + int material_id{-1}; + int backface_material_id{-1}; + + // USD GeomSubset.indices. Index to a facet, i.e. index to GeomMesh.faceVertexCounts[], in USD GeomSubset + std::vector usdIndices; + + // Triangulated indices. Filled when `MeshConverterConfig::triangualte` is true + std::vector triangulatedIndices; + + const std::vector &indices() const { + return triangulatedIndices.size() ? triangulatedIndices : usdIndices; + } + +}; + +// Currently normals and texcoords are converted as facevarying attribute. +struct RenderMesh { + // + // Type of Vertex attributes of this mesh. + // + // `Indexed` preferred. `Facevarying` as the last resport. + // + enum class VertexArrayType { + Indexed, // 'vertex'-varying. i.e, use faceVertexIndices to draw mesh. All + // vertex attributes must be representatable by single + // indices(i.e, no `facevertex`-varying attribute) + Facevarying, // 'facevertx'-varying. When any of mesh attribute has + // 'facevertex' varying, we cannot represent the mesh with + // single indices, so decompose all vertex attribute to + // Facevaring(no VertexArray indices). This would impact + // rendering performance. + }; + + std::string prim_name; // Prim name + std::string abs_path; // Absolute Prim path in Stage + std::string display_name; // `displayName` Prim metadataum + + VertexArrayType vertexArrayType{VertexArrayType::Facevarying}; + + std::vector points; // varying is 'vertex'. + + /// + /// Initialized with USD faceVertexIndices/faceVertexCounts in GeomMesh. + /// When the mesh is triangulated, these attribute does not change. + /// + /// But will be modified when `MeshConverterCondig::build_vertex_indices` is set to true + /// (To make vertex attributes of the mesh single-indexable) + /// + /// + std::vector usdFaceVertexIndices; + std::vector usdFaceVertexCounts; + + /// + /// Triangulated faceVertexIndices, faceVerteCounts and auxiality state + /// required to triangulate primvars in the app. + /// + /// trinangulated*** variables will be empty when the mesh is not + /// triangulated. + /// + /// Topology could be changed(modified) when `MeshConverterCondig::build_vertex_indices` is set to true. + /// + std::vector triangulatedFaceVertexIndices; + std::vector triangulatedFaceVertexCounts; + + std::vector + triangulatedToOrigFaceVertexIndexMap; // used for rearrange facevertex + // attrib + std::vector + triangulatedFaceCounts; // used for rearrange face indices(e.g GeomSubset + // indices) + + const std::vector &faceVertexIndices() const { + return is_triangulated() ? triangulatedFaceVertexIndices : usdFaceVertexIndices; + } + + const std::vector &faceVertexCounts() const { + return is_triangulated() ? triangulatedFaceVertexCounts : usdFaceVertexCounts; + } + + bool is_triangulated() const { + return triangulatedFaceVertexIndices.size() && triangulatedFaceVertexCounts.size(); + } + + // `normals` or `primvar:normals`. Empty when no normals exist in the + // GeomMesh. + VertexAttribute normals; + + // key = slot ID. Usually 0 = primary + std::unordered_map texcoords; + StringAndIdMap texcoordSlotIdMap; // st primvarname to slotID map + + // + // tangents and binormals(single-frame only) + // + // When `normals`(or `normals` primvar) is not present in the GeomMesh, + // tangents and normals are not computed. + // + // When `normals` is supplied, but neither `tangents` nor `binormals` are + // supplied in primvars, Tydra computes it based on: + // https://learnopengl.com/Advanced-Lighting/Normal-Mapping (when + // MeshConverterConfig::compute_tangents_and_binormals is set to `true`) + // + // For UsdPreviewSurface, geom primvar name of `tangents` and `binormals` are + // read from Material's inputs::frame:tangentsPrimvarName(default "tangents"), + // inputs::frame::binormalsPrimvarName(default "binormals") + // https://learnopengl.com/Advanced-Lighting/Normal-Mapping + // + VertexAttribute tangents; + VertexAttribute binormals; + + bool doubleSided{false}; // false = backface-cull. + value::color3f displayColor{ + 0.18f, 0.18f, + 0.18f}; // displayColor primvar(The number of array elements = 1) in USD. + // default is set to the same in UsdPreviewSurface::diffuseColor + float displayOpacity{ + 1.0}; // displayOpacity primvar(The number of array elements = 1) in USD + bool is_rightHanded{true}; // orientation attribute in USD. + + VertexAttribute + vertex_colors; // vertex color(displayColor primvar in USD). vec3. + VertexAttribute + vertex_opacities; // opacity(alpha) component of vertex + // color(displayOpacity primvar in USD). float + + // For vertex skinning + JointAndWeight joint_and_weights; + + // BlendShapes + // key = USD BlendShape prim name. + std::map targets; + + // Index to RenderScene::materials + int material_id{-1}; // Material applied to whole faces in the mesh. per-face + // material by GeomSubset is stored in + // `material_subsetMap` + int backface_material_id{ + -1}; // Backface material. Look up `rel material:binding:` + // in GeomMesh. BACKFACENAME is a user-supplied setting. Default = + // MaterialConverterConfig::default_backface_material_purpose_name + + // Key = GeomSubset name + std::map + material_subsetMap; // GeomSubset whose famiyName is 'materialBind' + + // If you want to access user-defined primvars or custom property, + // Plese look into corresponding Prim( stage::find_prim_at_path(abs_path) ) + + uint64_t handle{0}; // Handle ID for Graphics API. 0 = invalid +}; + +enum class UVReaderFloatComponentType { + COMPONENT_FLOAT, + COMPONENT_FLOAT2, + COMPONENT_FLOAT3, + COMPONENT_FLOAT4, +}; + +std::string to_string(UVReaderFloatComponentType ty); + +// float, float2, float3 or float4 only +struct UVReaderFloat { + UVReaderFloatComponentType componentType{ + UVReaderFloatComponentType::COMPONENT_FLOAT2}; + int64_t mesh_id{-1}; // index to RenderMesh + int64_t coord_id{-1}; // index to RenderMesh::facevaryingTexcoords + + // mat2 transform; // UsdTransform2d + + // Returns interpolated UV coordinate with UV transform + // # of components filled are equal to `componentType`. + vec4 fetchUV(size_t faceId, float varyu, float varyv); +}; + +struct UVTexture { + // NOTE: it looks no 'rgba' in UsdUvTexture + enum class Channel { R, G, B, A, RGB, RGBA }; + + std::string prim_name; // element Prim name + std::string abs_path; // Absolute Prim path + std::string display_name; // displayName prim metadatum + + // TextureWrap `black` in UsdUVTexture is mapped to `CLAMP_TO_BORDER`(app must + // set border color to black) default is CLAMP_TO_EDGE and `useMetadata` wrap + // mode is ignored. + enum class WrapMode { CLAMP_TO_EDGE, REPEAT, MIRROR, CLAMP_TO_BORDER }; + + WrapMode wrapS{WrapMode::CLAMP_TO_EDGE}; + WrapMode wrapT{WrapMode::CLAMP_TO_EDGE}; + + // Do CPU texture mapping. For baking texels with transform, texturing in + // raytracer(bake lighting), etc. + // + // This method accounts for `tranform` and `bias/scale` + // + // NOTE: for R, G, B channel, The value is replicated to output[0], output[1] + // and output[2]. For A channel, The value is returned to output[3] + vec4 fetch_uv(size_t faceId, float varyu, float varyv); + + // `fetch_uv` with user-specified channel. `outputChannel` is ignored. + vec4 fetch_uv_channel(size_t faceId, float varyu, float varyv, + Channel channel); + + // UVW version of `fetch_uv`. + vec4 fetch_uvw(size_t faceId, float varyu, float varyv, float varyw); + vec4 fetch_uvw_channel(size_t faceId, float varyu, float varyv, float varyw, + Channel channel); + + // output channel info + Channel outputChannel{Channel::RGB}; + + // bias and scale for texel value + vec4 bias{0.0f, 0.0f, 0.0f, 0.0f}; + vec4 scale{1.0f, 1.0f, 1.0f, 1.0f}; + + UVReaderFloat uvreader; + vec4 fallback_uv{0.0f, 0.0f, 0.0f, 0.0f}; + + // UsdTransform2d + // https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_transform + // = scale * rotate + translation + bool has_transform2d{false}; // true = `transform`, `tx_rotation`, `tx_scale` + // and `tx_translation` are filled; + mat3 transform{value::matrix3f::identity()}; + + // raw transform2d value + float tx_rotation{0.0f}; + vec2 tx_scale{1.0f, 1.0f}; + vec2 tx_translation{0.0f, 0.0f}; + + // UV primvars name(UsdPrimvarReader's inputs:varname) + std::string varname_uv; + + int64_t texture_image_id{-1}; // Index to TextureImage + uint64_t handle{0}; // Handle ID for Graphics API. 0 = invalid +}; + +std::string to_string(UVTexture::WrapMode ty); + +struct UDIMTexture { + enum class Channel { R, G, B, RGB, RGBA }; + + std::string prim_name; // element Prim name + std::string abs_path; // Absolute Prim path + std::string display_name; // displayName prim metadatum + + // NOTE: for single channel(e.g. R) fetch, Only [0] will be filled for the + // return value. + vec4 fetch(size_t faceId, float varyu, float varyv, float varyw = 1.0f, + Channel channel = Channel::RGB); + + // key = UDIM id(e.g. 1001) + std::unordered_map imageTileIds; +}; + +// T or TextureId +template +struct ShaderParam { + ShaderParam(const T &t) { value = t; } + + bool is_texture() const { return textureId >= 0; } + + template + void set_value(const STy &val) { + // Currently we assume T == Sty. + // TODO: support more type variant + static_assert(value::TypeTraits::underlying_type_id() == + value::TypeTraits::underlying_type_id(), + ""); + static_assert(sizeof(T) >= sizeof(STy), ""); + memcpy(&value, &val, sizeof(T)); + } + + T value; + int32_t textureId{-1}; // negative = invalid +}; + +// UsdPreviewSurface +struct PreviewSurfaceShader { + bool useSpecularWorkFlow{false}; + + ShaderParam diffuseColor{{0.18f, 0.18f, 0.18f}}; + ShaderParam emissiveColor{{0.0f, 0.0f, 0.0f}}; + ShaderParam specularColor{{0.0f, 0.0f, 0.0f}}; + ShaderParam metallic{0.0f}; + ShaderParam roughness{0.5f}; + ShaderParam clearcoat{0.0f}; + ShaderParam clearcoatRoughness{0.01f}; + ShaderParam opacity{1.0f}; + ShaderParam opacityThreshold{0.0f}; + ShaderParam ior{1.5f}; + ShaderParam normal{{0.0f, 0.0f, 1.0f}}; + ShaderParam displacement{0.0f}; + ShaderParam occlusion{0.0f}; + + uint64_t handle{0}; // Handle ID for Graphics API. 0 = invalid +}; + +// Material + Shader +struct RenderMaterial { + std::string name; // elementName in USD (e.g. "pbrMat") + std::string + abs_path; // abosolute Prim path in USD (e.g. "/_material/scope/pbrMat") + std::string display_name; + + PreviewSurfaceShader surfaceShader; + // TODO: displacement, volume. + + uint64_t handle{0}; // Handle ID for Graphics API. 0 = invalid +}; + +// Simple Camera +// +// https://openusd.org/dev/api/class_usd_geom_camera.html +// +// NOTE: Node's matrix is used for Camera matrix +// NOTE: "Y up" coordinate, right-handed coordinate space in USD. +// NOTE: Unit uses tenths of a scene unit(i.e. [mm] by default). +// RenderSceneConverter adjusts property value to [mm] accounting for Stage's unitsPerMeter +struct RenderCamera { + + std::string name; // elementName in USD (e.g. "frontCamera") + std::string + abs_path; // abosolute GeomCamera Prim path in USD (e.g. "/xform/camera") + std::string display_name; + + float znear{0.1f}; // clippingRange[0] + float zfar{1000000.0f}; // clippingRange[1] + float verticalAspectRatio{1.0}; // vertical aspect ratio + + // for Ortho camera + float xmag{1.0f}; // horizontal maginification + float ymag{1.0f}; // vertical maginification + + float focalLength{50.0f}; // EFL(Effective Focal Length). [mm] + float verticalAperture{15.2908f}; // [mm] + float horizontalAperture{20.965f}; // [mm] + + // vertical FOV in radian + inline float yfov() { + return 2.0f * std::atan(0.5f * verticalAperture / focalLength); + } + + // horizontal FOV in radian + float xfov() { + return 2.0f * std::atan(0.5f * horizontalAperture / focalLength); + } + + GeomCamera::Projection projection{GeomCamera::Projection::Perspective}; + GeomCamera::StereoRole stereoRole{GeomCamera::StereoRole::Mono}; + + double shutterOpen{0.0}; + double shutterClose{0.0}; + +}; + +// Simple light +struct RenderLight +{ + std::string name; // elementName in USD (e.g. "frontCamera") + std::string + abs_path; // abosolute GeomCamera Prim path in USD (e.g. "/xform/camera") + + + // TODO.. +}; + +struct SceneMetadata +{ + std::string copyright; + std::string comment; + + std::string upAxis{"Y"}; // "X", "Y" or "Z" + nonstd::optional startTimeCode; + nonstd::optional endTimeCode; + double framesPerSecond{24.0}; + double timeCodesPerSecond{24.0}; + double metersPerUnit{1.0}; // default [m] + + bool autoPlay{true}; + + // If you want to lookup more thing on USD Stage Metadata, Use Stage::metas() +}; + +// Simple glTF-like Scene Graph +class RenderScene { + public: + std::string usd_filename; + + SceneMetadata meta; + + uint32_t default_root_node{0}; // index to `nodes`. + + std::vector nodes; + std::vector images; + std::vector materials; + std::vector cameras; + std::vector lights; + std::vector textures; + std::vector meshes; + std::vector animations; + std::vector skeletons; + std::vector + buffers; // Various data storage(e.g. texel/image data). + +}; + +/// +/// Texture image loader callback +/// +/// The callback function should return TextureImage and Raw image data. +/// +/// NOTE: TextureImage::buffer_id is filled in Tydra side after calling this +/// callback. NOTE: TextureImage::colorSpace will be overwritten if +/// `asset:sourceColorSpace` is authored in UsdUVTexture. +/// +/// @param[in] asset Asset path +/// @param[in] assetInfo AssetInfo +/// @param[in] assetResolver AssetResolutionResolver context. Please pass +/// DefaultAssetResolutionResolver() if you don't have custom +/// AssetResolutionResolver. +/// @param[out] texImageOut TextureImage info. +/// @param[out] imageData Raw texture image data. +/// @param[inout] userdata User data. +/// @param[out] warn Optional. Warning message. +/// @param[out] error Optional. Error message. +/// +/// @return true upon success. +/// termination of visiting Prims. +/// +typedef bool (*TextureImageLoaderFunction)( + const value::AssetPath &assetPath, const AssetInfo &assetInfo, + const AssetResolutionResolver &assetResolver, TextureImage *imageOut, + std::vector *imageData, void *userdata, std::string *warn, + std::string *err); + +bool DefaultTextureImageLoaderFunction(const value::AssetPath &assetPath, + const AssetInfo &assetInfo, + const AssetResolutionResolver &assetResolver, + TextureImage *imageOut, + std::vector *imageData, + void *userdata, std::string *warn, + std::string *err); + +/// +/// TODO: UDIM loder +/// + +struct MeshConverterConfig { + bool triangulate{true}; + + bool validate_geomsubset{true}; // Validate GeomSubset. + + // We may want texcoord data even if the Mesh does not have bound Material. + // But we don't know which primvar is used as a texture coordinate when no + // Texture assigned to the mesh(no PrimVar Reader assigned to) Use + // UsdPreviewSurface setting for it. + // + // https://openusd.org/release/spec_usdpreviewsurface.html#usd-sample + // + // Also for tangnents/binormals. + // + // 'primvars' namespace is omitted. + // + std::string default_texcoords_primvar_name{"st"}; + std::string default_texcoords1_primvar_name{ + "st1"}; // for multi texture(available from iOS 16/macOS 13) + std::string default_tangents_primvar_name{"tangents"}; + std::string default_binormals_primvar_name{"binormals"}; + + // TODO: tangents1/binormals1 for multi-frame normal mapping? + + // Upperlimit of the number of skin weights per vertex. + // For realtime app, usually up to 64 + uint32_t max_skin_elementSize = 1024ull * 256ull; + + // + // Build vertex indices when vertex attributes are converted to `faceverying`? + // Similar vertices are merged into single vertex index. + // (convert vertex attributes from 'facevarying' to 'vertex' variability) + // + // Building indices is preferred for renderers which supports single + // index-buffer only (e.g. OpenGL/Vulkan) + // + bool build_vertex_indices{true}; + + // + // Compute normals if not present in the mesh. + // The algorithm computes smoothed normal for shared vertex. + // Normals are also computed when `compute_tangents_and_binormals` is true + // and normals primvar is not present in the mesh. + // + bool compute_normals{true}; + + // + // Compute tangents and binormals for tangent space normal mapping. + // But when primary texcoords primvar is not present, tangents and binormals are not computed. + // + // NOTE: The algorithm is not robust to compute tangent/binormal for quad/polygons. + // Set `triangulate` preferred when you want let Tydra compute tangent/binormal. + // + // NOTE: Computing tangent frame for multi-texcoord is not supported. + // + bool compute_tangents_and_binormals{true}; + + // + // Allowed relative error to check if vertex data is the same. + // Used for 'facevarying' variability to `vertex` variability conversion in + // ConvertMesh. Only effective to floating-point vertex data. + // + float facevarying_to_vertex_eps = std::numeric_limits::epsilon(); +}; + +struct MaterialConverterConfig { + // purpose name for two-sided material mapping. + // https://github.com/syoyo/tinyusdz/issues/120 + std::string default_backface_material_purpose_name{"back"}; + + // DefaultTextureImageLoader will be used when nullptr; + TextureImageLoaderFunction texture_image_loader_function{nullptr}; + void *texture_image_loader_function_userdata{nullptr}; + + // For UsdUVTexture. + // + // Default configuration: + // + // - The converter converts 8bit texture to floating point image and texel + // value is converted to linear space. + // - Allow missing asset(texture) and asset load failure. + // + // Recommended configuration for mobile/WebGL + // + // - `preserve_texel_bitdepth` true + // - No floating-point image conversion. + // - `linearize_color_space` true + // - Linearlize in CPU, and no sRGB -> Linear conversion in a shader + // required. + + // In the UsdUVTexture spec, 8bit texture image is converted to floating point + // image of range `[0.0, 1.0]`. When this flag is set to false, 8bit and 16bit + // texture image is converted to floating point image. When this flag is set + // to true, 8bit and 16bit texture data is stored as-is to save memory. + // Setting true is good if you want to render USD scene on mobile, WebGL, etc. + bool preserve_texel_bitdepth{false}; + + // Apply the inverse of a color space to make texture image in linear space. + // When `preserve_texel_bitdepth` is set to true, linearization also preserse + // texel bit depth (i.e, for 8bit sRGB image, 8bit linear-space image is + // produced) + bool linearize_color_space{false}; + + // Allow asset(texture, shader, etc) path with Windows backslashes(e.g. + // ".\textures\cat.png")? When true, convert it to forward slash('/') on + // Posixish system(otherwise character is escaped(e.g. '\t' -> tab). + bool allow_backslash_in_asset_path{true}; + + // Allow texture load failure? + bool allow_texture_load_failure{true}; + + // Allow asset(e.g. texture file/shader file) which does not exit? + bool allow_missing_asset{true}; + +}; + +struct RenderSceneConverterConfig { + // Load texture image data on convert. + // false: no actual texture file/asset access. + // App/User must setup TextureImage manually after the conversion. + bool load_texture_assets{true}; +}; + +// +// Simple packed vertex struct & comparator for dedup. +// https://github.com/huamulan/OpenGL-tutorial/blob/master/common/vboindexer.cpp +// +// Up to 2 texcoords. +// tangent and binormal is included in VertexData, considering the situation +// that tangent and binormal is supplied through user-defined primvar. +// +// TODO: Use spatial hash for robust dedup(consider floating-point eps) +// TODO: Polish interface to support arbitrary vertex configuration. +// +struct DefaultPackedVertexData { + //value::float3 position; + uint32_t point_index; + value::float3 normal; + value::float2 uv0; + value::float2 uv1; + value::float3 tangent; + value::float3 binormal; + value::float3 color; + float opacity; + + // comparator for std::map + bool operator<(const DefaultPackedVertexData &rhs) const { + return memcmp(reinterpret_cast(this), + reinterpret_cast(&rhs), + sizeof(DefaultPackedVertexData)) > 0; + } +}; + +struct DefaultPackedVertexDataHasher { + inline size_t operator()(const DefaultPackedVertexData &v) const { + // Simple hasher using FNV1 32bit + // TODO: Use 64bit FNV1? + // TODO: Use spatial hash or LSH(LocallySensitiveHash) for position value. + static constexpr uint32_t kFNV_Prime = 0x01000193; + static constexpr uint32_t kFNV_Offset_Basis = 0x811c9dc5; + + const uint8_t *ptr = reinterpret_cast(&v); + size_t n = sizeof(DefaultPackedVertexData); + + uint32_t hash = kFNV_Offset_Basis; + for (size_t i = 0; i < n; i++) { + hash = (kFNV_Prime * hash) ^ (ptr[i]); + } + + return size_t(hash); + } +}; + +struct DefaultPackedVertexDataEqual { + bool operator()(const DefaultPackedVertexData &lhs, + const DefaultPackedVertexData &rhs) const { + return memcmp(reinterpret_cast(&lhs), + reinterpret_cast(&rhs), + sizeof(DefaultPackedVertexData)) == 0; + } +}; + +template +struct DefaultVertexInput { + //std::vector positions; + std::vector point_indices; + std::vector normals; + std::vector uv0s; + std::vector uv1s; + std::vector tangents; + std::vector binormals; + std::vector colors; + std::vector opacities; + + size_t size() const { return point_indices.size(); } + + void get(size_t idx, PackedVert &output) const { + if (idx < point_indices.size()) { + output.point_index = point_indices[idx]; + } else { + output.point_index = ~0u; // this case should not happen though + } + if (idx < normals.size()) { + output.normal = normals[idx]; + } else { + output.normal = {0.0f, 0.0f, 0.0f}; + } + if (idx < uv0s.size()) { + output.uv0 = uv0s[idx]; + } else { + output.uv0 = {0.0f, 0.0f}; + } + if (idx < uv1s.size()) { + output.uv1 = uv1s[idx]; + } else { + output.uv1 = {0.0f, 0.0f}; + } + if (idx < tangents.size()) { + output.tangent = tangents[idx]; + } else { + output.tangent = {0.0f, 0.0f, 0.0f}; + } + if (idx < binormals.size()) { + output.binormal = binormals[idx]; + } else { + output.binormal = {0.0f, 0.0f, 0.0f}; + } + if (idx < colors.size()) { + output.color = colors[idx]; + } else { + output.color = {0.0f, 0.0f, 0.0f}; + } + if (idx < opacities.size()) { + output.opacity = opacities[idx]; + } else { + output.opacity = 0.0f; // FIXME: Use 1.0? + } + } +}; + +template +struct DefaultVertexOutput { + //std::vector positions; + std::vector point_indices; + std::vector normals; + std::vector uv0s; + std::vector uv1s; + std::vector tangents; + std::vector binormals; + std::vector colors; + std::vector opacities; + + size_t size() const { return point_indices.size(); } + + void push_back(const PackedVert &v) { + point_indices.push_back(v.point_index); + normals.push_back(v.normal); + uv0s.push_back(v.uv0); + uv1s.push_back(v.uv1); + tangents.push_back(v.tangent); + binormals.push_back(v.binormal); + colors.push_back(v.color); + opacities.push_back(v.opacity); + } +}; + +// +// out_vertex_indices_remap: corresponding vertexIndex in input. +// +template +void BuildIndices(const VertexInput &input, VertexOutput &output, + std::vector &out_indices, std::vector &out_point_indices) +{ + // TODO: Use LSH(locally sensitive hashing) or BVH for kNN point query. + std::unordered_map + vertexToIndexMap; + + auto GetSimilarVertex = [&](const PackedVert &v, uint32_t &out_idx) -> bool { + auto it = vertexToIndexMap.find(v); + if (it == vertexToIndexMap.end()) { + return false; + } + + out_idx = it->second; + return true; + }; + + for (size_t i = 0; i < input.size(); i++) { + PackedVert v; + input.get(i, v); + + uint32_t index{0}; + bool found = GetSimilarVertex(v, index); + if (found) { + out_indices.push_back(index); + } else { + uint32_t new_index = uint32_t(output.size()); + out_indices.push_back(new_index); + output.push_back(v); + vertexToIndexMap[v] = new_index; + } + out_point_indices.push_back(v.point_index); + } +} + +class RenderSceneConverterEnv { + public: + RenderSceneConverterEnv(const Stage &_stage) : stage(_stage) {} + + RenderSceneConverterConfig scene_config; + MeshConverterConfig mesh_config; + MaterialConverterConfig material_config; + + AssetResolutionResolver asset_resolver; + + std::string usd_filename; // Corresponding USD filename to Stage. + + void set_search_paths(const std::vector &paths) { + asset_resolver.set_search_paths(paths); + } + + const Stage &stage; // Point to valid Stage object at constructor + + double timecode{value::TimeCode::Default()}; + value::TimeSampleInterpolationType tinterp{ + value::TimeSampleInterpolationType::Linear}; + +}; + +// +// Convert USD scenegraph at specified time +// TODO: Use RenderSceneConverterEnv(RenderSceneConverterEnv::timecode) +// +class RenderSceneConverter { + public: + RenderSceneConverter() = default; + RenderSceneConverter(const RenderSceneConverter &rhs) = delete; + RenderSceneConverter(RenderSceneConverter &&rhs) = delete; + + /// + /// All-in-one Stage to RenderScene conversion. + /// + /// Convert Stage to RenderScene. + /// Must be called after SetStage, SetMaterialConverterConfig(optional) + /// + bool ConvertToRenderScene(const RenderSceneConverterEnv &env, RenderScene *scene); + + const std::string &GetInfo() const { return _info; } + const std::string &GetWarning() const { return _warn; } + const std::string &GetError() const { return _err; } + + // Prim path <-> index for corresponding array + // e.g. meshMap: primPath/index to `meshes`. + + // TODO: Move to private? + StringAndIdMap root_nodeMap; + StringAndIdMap meshMap; + StringAndIdMap materialMap; + StringAndIdMap cameraMap; + StringAndIdMap lightMap; + StringAndIdMap textureMap; + StringAndIdMap imageMap; + StringAndIdMap bufferMap; + + int default_node{-1}; + + std::vector root_nodes; + std::vector meshes; + std::vector materials; + std::vector cameras; + std::vector lights; + std::vector textures; + std::vector images; + std::vector buffers; + std::vector skeletons; + + /// + /// Convert GeomMesh to renderer-friendly mesh. + /// Also apply triangulation when MeshConverterConfig::triangulate is set to + /// true. + /// + /// normals, texcoords, vertexcolors/opacities vertex attributes(built-in + /// primvars) are converterd to either `vertex` variability(i.e. can be drawn + /// with single vertex indices) or `facevarying` variability(any of primvars + /// is `facevarying`. It can be drawn with no indices, but less + /// efficient(especially vertex has skin weights and blendshapes)). + /// + /// Since preferred variability for OpenGL/Vulkan renderer is `vertex`, + /// ConvertMesh tries to convert `facevarying` attribute to `vertex` attribute + /// when all shared vertex data is the same. If it fails, but + /// `MeshConverterConfig.build_indices` is set to true, ConvertMesh builds + /// vertex indices from `facevarying` and convert variability to 'vertex'. + /// + /// Note that `points`, skin weights and BlendShape attributes are remains + /// with `vertex` variability. (so that we can apply some processing per + /// point-wise) + /// + /// Thus, if you want to render a mesh whose normal/texcoord/etc variability + /// is `facevarying`, `points`, skin weights and BlendShape attributes would + /// also need to be converted to `facevarying` to draw. + /// + /// Other user defined primvars are not touched by ConvertMesh. + /// The app need to manually triangulate, change variability of user-defined + /// primvar if required. + /// + /// It is recommended first convert Materials assigned(bounded) to this + /// GeomMesh(and GeomSubsets) or create your own Materials, and supply + /// material info with `material_path` and `rmaterial_map`. You may supply + /// empty material info and assign Material after ConvertMesh manually, but it + /// will need some steps(Need to find texcoord primvar, triangulate texcoord, + /// etc). See the implementation of ConvertMesh for details) + /// + /// + /// @param[in] mesh_abs_path USD prim path to this GeomMesh + /// @param[in] mesh Input GeomMesh + /// @param[in] material_path USD Material Prim path assigned(bound) to this + /// GeomMesh. Use tydra::GetBoundPath to get Material path actually assigned + /// to the mesh. + /// @param[in] subset_material_path_map USD Material Prim path assigned(bound) + /// to GeomSubsets in this GeomMesh. key = GeomSubset Prim name. + /// @param[in] rmaterial_map USD Material Prim path <-> RenderMaterial index + /// map. Use empty map if no material assigned to this Mesh. If the mesh has + /// bounded material(including material from GeomSubset), RenderMaterial index + /// must be obrained using ConvertMaterial method before calling ConvertMesh. + /// @param[in] material_subsets GeomSubset assigned to this Mesh. Can be empty + /// when no materialBind GeomSuset assigned to this mesh. + /// @param[in] blendshapes BlendShape Prims assigned to this Mesh. Can be + /// empty when no BlendShape assigned to this mesh. + /// @param[out] dst RenderMesh output + /// + /// @return true when success. + /// + /// + bool ConvertMesh( + const RenderSceneConverterEnv &env, const tinyusdz::Path &mesh_abs_path, + const tinyusdz::GeomMesh &mesh, const MaterialPath &material_path, + const std::map &subset_material_path_map, + //const std::map &rmaterial_map, + const StringAndIdMap &rmaterial_map, + const std::vector &material_subsets, + const std::vector> + &blendshapes, + RenderMesh *dst); + + /// + /// Convert USD Material/Shader to renderer-friendly Material + /// + /// @return true when success. + /// + bool ConvertMaterial(const RenderSceneConverterEnv &env, + const tinyusdz::Path &abs_mat_path, + const tinyusdz::Material &material, + RenderMaterial *rmat_out); + + /// + /// Convert UsdPreviewSurface Shader to renderer-friendly PreviewSurfaceShader + /// + /// @param[in] shader_abs_path USD Path to Shader Prim with UsdPreviewSurface + /// info:id. + /// @param[in] shader UsdPreviewSurface + /// @param[in] pss_put PreviewSurfaceShader + /// + /// @return true when success. + /// + bool ConvertPreviewSurfaceShader(const RenderSceneConverterEnv &env, + const tinyusdz::Path &shader_abs_path, + const tinyusdz::UsdPreviewSurface &shader, + PreviewSurfaceShader *pss_out); + + /// + /// Convert UsdUvTexture to renderer-friendly UVTexture + /// + /// @param[in] tex_abs_path USD Path to Shader Prim with UsdUVTexture info:id. + /// @param[in] assetInfo assetInfo Prim metadata of given Shader Prim + /// @param[in] texture UsdUVTexture + /// @param[in] tex_out UVTexture + /// + /// TODO: Retrieve assetInfo from `tex_abs_path`? + /// + /// @return true when success. + /// + bool ConvertUVTexture(const RenderSceneConverterEnv &env, + const Path &tex_abs_path, const AssetInfo &assetInfo, + const UsdUVTexture &texture, UVTexture *tex_out); + + /// + /// Convert SkelAnimation to Tydra Animation. + /// + /// @param[in] abs_path USD Path to SkelAnimation Prim + /// @param[in] skelAnim SkelAnimation + /// @param[in] anim_out UVTexture + /// + bool ConvertSkelAnimation(const RenderSceneConverterEnv &env, + const Path &abs_path, const SkelAnimation &skelAnim, + Animation *anim_out); + + /// + /// @param[in] env + /// @param[in] root XformNode + /// + bool BuildNodeHierarchy(const RenderSceneConverterEnv &env, const XformNode &node); + + private: + /// + /// Convert variability of vertex data to 'vertex' or 'facevarying'. + /// + /// @param[inout] vattr Input/Output VertexAttribute + /// @param[in] to_vertex_varing Convert to `vertex` varying when true. + /// `facevarying` when false. + /// @param[in] faceVertexCounts faceVertexCounts + /// @param[in] faceVertexIndices faceVertexIndices + /// + /// @return true upon success. + /// + bool ConvertVertexVariabilityImpl( + VertexAttribute &vattr, const bool to_vertex_varying, + const std::vector &faceVertexCounts, + const std::vector &faceVertexIndices); + + template + bool ConvertPreviewSurfaceShaderParam( + const RenderSceneConverterEnv &env, const Path &shader_abs_path, + const TypedAttributeWithFallback> ¶m, + const std::string ¶m_name, ShaderParam &dst_param); + + /// + /// Build (single) vertex indices for RenderMesh. + /// existing `RenderMesh::faceVertexIndices` will be replaced with built indices. + /// All vertex attributes are converted to 'vertex' variability. + /// + /// Limitation: Currently we only supports texcoords up to two(primary(0) and secondary(1)). + /// + /// @param[inout] mesh + /// + bool BuildVertexIndicesImpl(RenderMesh &mesh); + + // + // Get Skeleton assigned to this GeomMesh Prim + // + bool GetSkeletonImpl(const tinyusdz::Prim &prim, + const tinyusdz::Skeleton *&out_skeleton); + + bool BuildNodeHierarchyImpl( + const RenderSceneConverterEnv &env, + const std::string &parentPrimPath, + const XformNode &node, + Node &out_rnode); + + void PushInfo(const std::string &msg) { _info += msg; } + void PushWarn(const std::string &msg) { _warn += msg; } + void PushError(const std::string &msg) { _err += msg; } + + std::string _info; + std::string _err; + std::string _warn; +}; + +// For debug +// Supported format: "kdl" (default. https://kdl.dev/), "json" +// +std::string DumpRenderScene(const RenderScene &scene, + const std::string &format = "kdl"); + +} // namespace tydra +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/scene-access.cc b/contrib/tinyusdz/tinyusdz_repo/src/tydra/scene-access.cc new file mode 100644 index 000000000..220ef13ab --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/scene-access.cc @@ -0,0 +1,2678 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022-Present Light Transport Entertainment, Inc. +// + +// src +#include "common-macros.inc" +#include "pprinter.hh" +#include "prim-pprint.hh" +#include "prim-types.hh" +#include "primvar.hh" +#include "tiny-format.hh" +#include "tydra/prim-apply.hh" +#include "usdGeom.hh" +#include "usdLux.hh" +#include "usdShade.hh" +#include "usdSkel.hh" +#include "value-pprint.hh" + +// src/tydra +#include "scene-access.hh" +#include "attribute-eval.hh" + +#include "../../../assimp_tinyusdz_logging.inc" + +namespace tinyusdz { +namespace tydra { + +constexpr auto kInfoId = "info:id"; + +namespace { + +// For PUSH_ERROR_AND_RETURN +#define PushError(msg) \ + if (err) { \ + (*err) += msg; \ + } + +// Typed TimeSamples to typeless TimeSamples +template +value::TimeSamples ToTypelessTimeSamples(const TypedTimeSamples &ts) { + const std::vector::Sample> &samples = + ts.get_samples(); + + value::TimeSamples dst; + + for (size_t i = 0; i < samples.size(); i++) { + dst.add_sample(samples[i].t, samples[i].value); + } + + return dst; +} + +// Enum TimeSamples to typeless TimeSamples +template +value::TimeSamples EnumTimeSamplesToTypelessTimeSamples( + const TypedTimeSamples &ts) { + const std::vector::Sample> &samples = + ts.get_samples(); + + value::TimeSamples dst; + + for (size_t i = 0; i < samples.size(); i++) { + // to token + value::token tok(to_string(samples[i].value)); + dst.add_sample(samples[i].t, tok); + } + + return dst; +} + +template +bool TraverseRec(const std::string &path_prefix, const tinyusdz::Prim &prim, + uint32_t depth, PathPrimMap &itemmap) { + if (depth > 1024 * 128) { + // Too deep + return false; + } + + std::string prim_abs_path = + path_prefix + "/" + prim.local_path().full_path_name(); + + if (prim.is()) { + if (const T *pv = prim.as()) { + std::cout << "Path : <" << prim_abs_path << "> is " + << tinyusdz::value::TypeTraits::type_name() << ".\n"; + itemmap[prim_abs_path] = pv; + } + } + + for (const auto &child : prim.children()) { + if (!TraverseRec(prim_abs_path, child, depth + 1, itemmap)) { + return false; + } + } + + return true; +} + +// Specialization for Shader type. +template +bool TraverseShaderRec(const std::string &path_prefix, + const tinyusdz::Prim &prim, uint32_t depth, + PathShaderMap &itemmap) { + if (depth > 1024 * 128) { + // Too deep + return false; + } + + std::string prim_abs_path = + path_prefix + "/" + prim.local_path().full_path_name(); + + // First check if type is Shader Prim. + if (const Shader *ps = prim.as()) { + // Then check if wanted Shder type + if (const ShaderTy *s = ps->value.as()) { + itemmap[prim_abs_path] = std::make_pair(ps, s); + } + } + + for (const auto &child : prim.children()) { + if (!TraverseShaderRec(prim_abs_path, child, depth + 1, itemmap)) { + return false; + } + } + + return true; +} + +bool ListSceneNamesRec(const tinyusdz::Prim &root, uint32_t depth, + std::vector> *sceneNames) { + if (!sceneNames) { + return false; + } + + if (depth > 1024 * 128) { + // Too deep + return false; + } + + if (root.metas().sceneName.has_value()) { + bool is_over = (root.specifier() == Specifier::Over); + + sceneNames->push_back( + std::make_pair(is_over, root.metas().sceneName.value())); + } + + return true; +} + +} // namespace + +template +bool ListPrims(const tinyusdz::Stage &stage, PathPrimMap &m /* output */) { + // Should report error at compilation stege. + static_assert( + (value::TypeId::TYPE_ID_MODEL_BEGIN <= value::TypeTraits::type_id()) && + (value::TypeId::TYPE_ID_MODEL_END > value::TypeTraits::type_id()), + "Not a Prim type."); + + // Check at runtime. Just in case... + if ((value::TypeId::TYPE_ID_MODEL_BEGIN <= value::TypeTraits::type_id()) && + (value::TypeId::TYPE_ID_MODEL_END > value::TypeTraits::type_id())) { + // Ok + } else { + return false; + } + + for (const auto &root_prim : stage.root_prims()) { + TraverseRec(/* root path is empty */ "", root_prim, /* depth */ 0, m); + } + + return true; +} + +template +bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m /* output */) { + // Concrete Shader type(e.g. UsdPreviewSurface) is classified as Imaging + // Should report error at compilation stege. + static_assert( + (value::TypeId::TYPE_ID_IMAGING_BEGIN <= value::TypeTraits::type_id()) && + (value::TypeId::TYPE_ID_IMAGING_END > value::TypeTraits::type_id()), + "Not a Shader type."); + + // Check at runtime. Just in case... + if ((value::TypeId::TYPE_ID_IMAGING_BEGIN <= value::TypeTraits::type_id()) && + (value::TypeId::TYPE_ID_IMAGING_END > value::TypeTraits::type_id())) { + // Ok + } else { + return false; + } + + for (const auto &root_prim : stage.root_prims()) { + TraverseShaderRec(/* root path is empty */ "", root_prim, /* depth */ 0, m); + } + + return true; +} + +const Prim *GetParentPrim(const tinyusdz::Stage &stage, + const tinyusdz::Path &path, std::string *err) { + if (!path.is_valid()) { + if (err) { + (*err) = "Input Path " + tinyusdz::to_string(path) + " is invalid.\n"; + } + return nullptr; + } + + if (path.is_root_path()) { + if (err) { + (*err) = "Input Path is root(\"/\").\n"; + } + return nullptr; + } + + if (path.is_root_prim()) { + if (err) { + (*err) = "Input Path is root Prim, so no parent Prim exists.\n"; + } + return nullptr; + } + + if (!path.is_absolute_path()) { + if (err) { + (*err) = "Input Path must be absolute path(i.e. starts with \"/\").\n"; + } + return nullptr; + } + + tinyusdz::Path parentPath = path.get_parent_prim_path(); + + nonstd::expected ret = + stage.GetPrimAtPath(parentPath); + if (ret) { + return ret.value(); + } else { + if (err) { + (*err) += "Failed to get parent Prim from Path " + + tinyusdz::to_string(path) + ". Reason = " + ret.error() + "\n"; + } + return nullptr; + } +} + +// +// Template Instanciations +// +#define LISTPRIMS_INSTANCIATE(__ty) \ +template bool ListPrims(const tinyusdz::Stage &stage, PathPrimMap<__ty> &m); + +APPLY_FUNC_TO_PRIM_TYPES(LISTPRIMS_INSTANCIATE) + +#undef LISTPRIMS_INSTANCIATE + +template bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m); +template bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m); + +template bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m); +template bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m); +template bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m); +template bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m); +template bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m); +template bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m); +template bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m); + +namespace { + +bool VisitPrimsRec(const tinyusdz::Path &root_abs_path, const tinyusdz::Prim &root, int32_t level, + VisitPrimFunction visitor_fun, void *userdata, std::string *err) { + + std::string fun_error; + bool ret = visitor_fun(root_abs_path, root, level, userdata, &fun_error); + if (!ret) { + if (fun_error.empty()) { + // early termination request. + DCOUT("Early termination requested"); + } else { + if (err) { + (*err) += fmt::format("Visit function returned an error for Prim {} (id {}). err = {}", root_abs_path.full_path_name(), root.prim_id(), fun_error); + } + } + return false; + } + + // if `primChildren` is available, use it + if (root.metas().primChildren.size() == root.children().size()) { + std::map primNameTable; + for (size_t i = 0; i < root.children().size(); i++) { + primNameTable.emplace(root.children()[i].element_name(), &root.children()[i]); + } + + for (size_t i = 0; i < root.metas().primChildren.size(); i++) { + value::token nameTok = root.metas().primChildren[i]; + const auto it = primNameTable.find(nameTok.str()); + if (it != primNameTable.end()) { + const Path child_abs_path = root_abs_path.AppendPrim(nameTok.str()); + if (!VisitPrimsRec(child_abs_path, *it->second, level + 1, visitor_fun, userdata, err)) { + return false; + } + } else { + if (err) { + (*err) += fmt::format("Prim name `{}` in `primChildren` metadatum not found in this Prim's children", nameTok.str()); + } + return false; + } + } + + } else { + + for (const auto &child : root.children()) { + const Path child_abs_path = root_abs_path.AppendPrim(child.element_name()); + if (!VisitPrimsRec(child_abs_path, child, level + 1, visitor_fun, userdata, err)) { + return false; + } + } + + } + + return true; +} + +#if 0 +// Scalar-valued attribute. +// TypedAttribute* => Attribute defined in USD schema, so not a custom attr. +template +void ToProperty( + const TypedAttributeWithFallback &input, + Property &output) +{ + if (input.IsBlocked()) { + Attribute attr; + attr.set_blocked(input.IsBlocked()); + attr.variability() = Variability::Uniform; + output = Property(std::move(attr), /*custom*/ false); + } else if (input.IsValueEmpty()) { + // type info only + output = Property(value::TypeTraits::type_name(), /* custom */false); + } else if (input.IsConnection()) { + + // Use Relation for Connection(as typed relationshipTarget) + // Single connection targetPath only. + Relation relTarget; + if (auto pv = input.GetConnection()) { + relTarget.targetPath = pv.value(); + } else { + // ??? TODO: report internal error. + } + output = Property(relTarget, /* type */value::TypeTraits::type_name(), /* custom */false); + + } else { + // Includes !authored() + value::Value val(input.GetValue()); + primvar::PrimVar pvar; + pvar.set_value(val); + Attribute attr; + attr.set_var(std::move(pvar)); + attr.variability() = Variability::Uniform; + output = Property(attr, /* custom */false); + } +} +#endif + +// Scalar-valued attribute. +// TypedAttribute* => Attribute defined in USD schema, so not a custom attr. +template +bool ToProperty(const TypedAttribute &input, Property &output, std::string *err) { + if (input.is_blocked()) { + Attribute attr; + attr.set_blocked(input.is_blocked()); + attr.variability() = Variability::Uniform; + attr.set_type_name(value::TypeTraits::type_name()); + output = Property(std::move(attr), /*custom*/ false); + } else if (input.is_value_empty()) { + // type info only + output = Property::MakeEmptyAttrib(value::TypeTraits::type_name(), /* custom */ false); + } else if (input.is_connection()) { + // Use Relation for Connection(as typed relationshipTarget) + // Single connection targetPath only. + Relationship relTarget; + std::vector paths = input.get_connections(); + if (paths.empty()) { + if (err) { + (*err) += fmt::format("[InternalError] Connection attribute but empty targetPaths."); + } + return false; + } else if (paths.size() == 1) { + output = Property(paths[0], /* type */ value::TypeTraits::type_name(), + /* custom */ false); + } else { + output = Property(paths, /* type */ value::TypeTraits::type_name(), + /* custom */ false); + } + + } else { + // Includes !authored() + if (auto pv = input.get_value()) { + value::Value val(pv.value()); + primvar::PrimVar pvar; + pvar.set_value(val); + Attribute attr; + attr.set_var(std::move(pvar)); + attr.variability() = Variability::Uniform; + output = Property(attr, /* custom */ false); + } else { + if (err) { + (*err) += fmt::format("[InternalError] Invalid TypedAttribute<{}> value.", value::TypeTraits::type_name()); + } + + return false; + } + } + + return true; +} + +// Scalar or TimeSample-valued attribute. +// TypedAttribute* => Attribute defined in USD schema, so not a custom attr. +// +// TODO: Support timeSampled attribute. +template +bool ToProperty(const TypedAttribute> &input, Property &output, std::string *err) { + if (input.is_blocked()) { + DCOUT("Value is blocked"); + Attribute attr; + attr.set_blocked(input.is_blocked()); + attr.variability() = Variability::Uniform; + attr.set_type_name(value::TypeTraits::type_name()); + output = Property(std::move(attr), /*custom*/ false); + return true; + } else if (input.is_value_empty()) { + DCOUT("Value is empty"); + // type info only + output = Property::MakeEmptyAttrib(value::TypeTraits::type_name(), /* custom */ false); + return true; + } else if (input.is_connection()) { + DCOUT("IsConnection"); + + // Use Relation for Connection(as typed relationshipTarget) + // Single connection targetPath only. + std::vector pv = input.get_connections(); + if (pv.empty()) { + DCOUT("??? Empty connectionTarget."); + + if (err) { + (*err) += fmt::format("[InternalError] Connection attribute but empty targetPaths."); + } + return false; + } + if (pv.size() == 1) { + DCOUT("targetPath = " << pv[0]); + output = Property(pv[0], /* type */ value::TypeTraits::type_name(), + /* custom */ false); + } else if (pv.size() > 1) { + DCOUT("targetPath = " << pv); + output = Property(pv, /* type */ value::TypeTraits::type_name(), + /* custom */ false); + } else { + DCOUT("??? GetConnection failue."); + if (err) { + (*err) += fmt::format("[InternalError] get_connection() to TypedAttribute> failed.", value::TypeTraits::type_name()); + } + return false; + } + + return true; + + } else { + // Includes !authored() + // FIXME: Currently scalar only. + nonstd::optional> aval = input.get_value(); + if (aval) { + DCOUT("Value is authored"); + if (aval.value().is_scalar()) { + DCOUT("Value is scalar"); + T a; + if (aval.value().get_scalar(&a)) { + DCOUT("Value get ok"); + //value::Value val(a); + Attribute attr; + //attr.set_type_name(value::TypeTraits::type_name()); + primvar::PrimVar pvar; + pvar.set_value(a); + attr.set_var(std::move(pvar)); + //attr.set_value(val); + attr.variability() = Variability::Varying; + output = Property(attr, /* custom */ false); + return true; + } else { + DCOUT("Value get failed."); + } + } else if (aval.value().is_blocked()) { + DCOUT("Value is blocked"); + Attribute attr; + attr.set_type_name(value::TypeTraits::type_name()); + attr.set_blocked(true); + attr.variability() = Variability::Varying; + output = Property(std::move(attr), /*custom*/ false); + return true; + } else if (aval.value().is_timesamples()) { + DCOUT("TODO: Convert typed TimeSamples to value::TimeSamples"); + if (err) { + (*err) += fmt::format("[TODO] TimeSamples value of TypedAttribute> is not yet implemented.", value::TypeTraits::type_name()); + } + return false; + } + } else { + DCOUT("Value is not authored"); + } + } + + // fallback to Property with invalid value + DCOUT("Fallback to invalid value"); + Property p; + p.set_property_type(Property::Type::EmptyAttrib); + p.attribute().set_type_name(value::TypeTraits::type_name()); + p.set_custom(false); + + output = p; + + return true; +} + +// Scalar or TimeSample-valued attribute. +// TypedAttribute* => Attribute defined in USD schema, so not a custom attr. +// +// TODO: Support timeSampled attribute. +template +bool ToProperty(const TypedAttributeWithFallback> &input, + Property &output, std::string *err) { + if (input.is_blocked()) { + Attribute attr; + attr.set_blocked(input.is_blocked()); + attr.variability() = Variability::Uniform; + attr.set_type_name(value::TypeTraits::type_name()); + output = Property(std::move(attr), /*custom*/ false); + } else if (input.is_value_empty()) { + // type info only + Property p; + p.set_property_type(Property::Type::EmptyAttrib); + p.attribute().set_type_name(value::TypeTraits::type_name()); + p.set_custom(false); + output = p; + } else if (input.is_connection()) { + // Use Relation for Connection(as typed relationshipTarget) + // Single connection targetPath only. + Relationship rel; + std::vector pv = input.get_connections(); + if (pv.empty()) { + DCOUT("??? Empty connectionTarget."); + if (err) { + (*err) += "[InternalError] Empty connectionTarget."; + } + return false; + } + if (pv.size() == 1) { + DCOUT("targetPath = " << pv[0]); + output = Property(pv[0], /* type */ value::TypeTraits::type_name(), + /* custom */ false); + } else if (pv.size() > 1) { + output = Property(pv, /* type */ value::TypeTraits::type_name(), + /* custom */ false); + } else { + DCOUT("??? GetConnection faile."); + if (err) { + (*err) += "[InternalError] Invalid connectionTarget."; + } + return false; + } + + } else { + // Includes !authored() + // FIXME: Currently scalar only. + Animatable v = input.get_value(); + + primvar::PrimVar pvar; + + if (v.is_timesamples()) { + value::TimeSamples ts = ToTypelessTimeSamples(v.get_timesamples()); + pvar.set_timesamples(ts); + } else if (v.is_scalar()) { + T a; + if (v.get_scalar(&a)) { + value::Value val(a); + pvar.set_value(val); + } else { + DCOUT("??? Invalid Animatable value."); + if (err) { + (*err) += "[InternalError] Invalid Animatable value."; + } + return false; + } + } else { + DCOUT("??? Invalid Animatable value."); + if (err) { + (*err) += "[InternalError] Invalid Animatable value."; + } + return false; + } + + Attribute attr; + attr.set_var(std::move(pvar)); + attr.variability() = Variability::Varying; + output = Property(attr, /* custom */ false); + } + + return true; +} + +// To Property with token type +template +bool ToTokenProperty(const TypedAttributeWithFallback> &input, + Property &output, std::string *err) { + if (input.is_blocked()) { + Attribute attr; + attr.set_blocked(input.is_blocked()); + attr.variability() = Variability::Uniform; + attr.set_type_name(value::kToken); + output = Property(std::move(attr), /*custom*/ false); + } else if (input.is_value_empty()) { + // type info only + Property p; + p.set_property_type(Property::Type::EmptyAttrib); + p.attribute().set_type_name(value::kToken); + p.set_custom(false); + output = p; + } else if (input.is_connection()) { + // Use Relation for Connection(as typed relationshipTarget) + // Single connection targetPath only. + Relationship rel; + std::vector pv = input.get_connections(); + if (pv.empty()) { + if (err) { + (*err) += "Empty targetPaths."; + } + return false; + } + if (pv.size() == 1) { + output = Property(pv[0], /* type */ value::kToken, /* custom */ false); + } else if (pv.size() > 1) { + output = Property(pv, /* type */ value::kToken, /* custom */ false); + } else { + if (err) { + (*err) += "[InternalError] Invalid targetPaths."; + } + return false; + } + + } else { + // Includes !authored() + // FIXME: Currently scalar only. + Animatable v = input.get_value(); + + primvar::PrimVar pvar; + + if (v.is_timesamples()) { + value::TimeSamples ts = + EnumTimeSamplesToTypelessTimeSamples(v.get_timesamples()); + pvar.set_timesamples(ts); + } else if (v.is_scalar()) { + T a; + if (v.get_scalar(&a)) { + // to token type + value::token tok(to_string(a)); + value::Value val(tok); + pvar.set_value(val); + } else { + if (err) { + (*err) += "[InternalError] Invalid Animatable value."; + } + return false; + } + } else { + if (err) { + (*err) += "[InternalError] Invalid Animatable value."; + } + return false; + } + + Attribute attr; + attr.set_var(std::move(pvar)); + attr.variability() = Variability::Varying; + output = Property(attr, /* custom */ false); + } + + return true; +} + +// To Property with token type +template +bool ToTokenProperty(const TypedAttributeWithFallback &input, + Property &output, std::string *err) { + if (input.is_blocked()) { + Attribute attr; + attr.set_blocked(input.is_blocked()); + attr.variability() = Variability::Uniform; + attr.set_type_name(value::kToken); + output = Property(std::move(attr), /*custom*/ false); + } else if (input.is_value_empty()) { + // type info only + Property p; + p.set_property_type(Property::Type::EmptyAttrib); + p.attribute().set_type_name(value::kToken); + p.set_custom(false); + output = p; + } else if (input.is_connection()) { + // Use Relation for Connection(as typed relationshipTarget) + // Single connection targetPath only. + Relationship rel; + std::vector pv = input.get_connections(); + if (pv.empty()) { + DCOUT("??? Empty connectionTarget."); + if (err) { + (*err) += "Empty connectionTarget."; + } + return false; + } + if (pv.size() == 1) { + output = Property(pv[0], /* type */ value::kToken, /* custom */ false); + } else if (pv.size() > 1) { + output = Property(pv, /* type */ value::kToken, /* custom */ false); + } else { + if (err) { + (*err) += "[InternalError] Get connectionTarget failed."; + } + return false; + } + + } else { + // Includes !authored() + // FIXME: Currently scalar only. + Animatable v = input.get_value(); + + primvar::PrimVar pvar; + + if (v.is_scalar()) { + T a; + if (v.get_scalar(&a)) { + // to token type + value::token tok(to_string(a)); + value::Value val(tok); + pvar.set_value(val); + } else { + if (err) { + (*err) += "[InternalError] Invalid value."; + } + return false; + } + } else { + if (err) { + (*err) += "[InternalError] Invalid value."; + } + return false; + } + + Attribute attr; + attr.set_var(std::move(pvar)); + attr.variability() = Variability::Uniform; + output = Property(attr, /* custom */ false); + } + + return true; +} + +template +nonstd::optional TypedTerminalAttributeToProperty(const TypedTerminalAttribute &input) { + if (!input.authored()) { + // nothing to do + return nonstd::nullopt; + } + + Property output; + + // type info only + if (input.has_actual_type()) { + // type info only + output = Property::MakeEmptyAttrib(input.get_actual_type_name(), /* custom */ false); + } else { + output = Property::MakeEmptyAttrib(input.type_name(), /* custom */ false); + } + + return output; +} + + +bool XformOpToProperty(const XformOp &x, Property &prop) { + primvar::PrimVar pv; + + Attribute attr; + + switch (x.op_type) { + case XformOp::OpType::ResetXformStack: { + // ??? Not exists in Prim's property + return false; + } + case XformOp::OpType::Transform: + case XformOp::OpType::Scale: + case XformOp::OpType::Translate: + case XformOp::OpType::RotateX: + case XformOp::OpType::RotateY: + case XformOp::OpType::RotateZ: + case XformOp::OpType::Orient: + case XformOp::OpType::RotateXYZ: + case XformOp::OpType::RotateXZY: + case XformOp::OpType::RotateYXZ: + case XformOp::OpType::RotateYZX: + case XformOp::OpType::RotateZXY: + case XformOp::OpType::RotateZYX: { + pv = x.get_var(); + } + } + + attr.set_var(std::move(pv)); + // TODO: attribute meta + + prop = Property(attr, /* custom */ false); + + return true; +} + +#define TO_PROPERTY(__prop_name, __v) \ + if (prop_name == __prop_name) { \ + if (!ToProperty(__v, *out_prop, &err)) { \ + return nonstd::make_unexpected(fmt::format("Convert Property {} failed: {}\n", __prop_name, err)); \ + } \ + } else + +#define TO_TOKEN_PROPERTY(__prop_name, __v) \ + if (prop_name == __prop_name) { \ + if (!ToTokenProperty(__v, *out_prop, &err)) { \ + return nonstd::make_unexpected(fmt::format("Convert Property {} failed: {}\n", __prop_name, err)); \ + } \ + } else + +// Return false: something went wrong +// `attr_prop` true: Include Attribute property. +// `rel_prop` true: Include Relationship property. +template +bool GetPrimPropertyNamesImpl( + const T &prim, std::vector *prop_names, bool attr_prop=true, bool rel_prop=true); + + +// Return true: Property found(`out_prop` filled) +// Return false: Property not found +// Return unexpected: Some eror happened. +template +nonstd::expected GetPrimProperty( + const T &prim, const std::string &prop_name, Property *out_prop); + +template <> +nonstd::expected GetPrimProperty( + const Model &model, const std::string &prop_name, Property *out_prop) { + if (!out_prop) { + return nonstd::make_unexpected( + "[InternalError] nullptr in output Property is not allowed."); + } + + const auto it = model.props.find(prop_name); + if (it == model.props.end()) { + // Attribute not found. + return false; + } + + (*out_prop) = it->second; + + return true; +} + +template <> +nonstd::expected GetPrimProperty( + const Scope &scope, const std::string &prop_name, Property *out_prop) { + if (!out_prop) { + return nonstd::make_unexpected( + "[InternalError] nullptr in output Property is not allowed."); + } + + const auto it = scope.props.find(prop_name); + if (it == scope.props.end()) { + // Attribute not found. + return false; + } + + (*out_prop) = it->second; + + return true; +} + +template <> +nonstd::expected GetPrimProperty( + const Xform &xform, const std::string &prop_name, Property *out_prop) { + if (!out_prop) { + return nonstd::make_unexpected( + "[InternalError] nullptr in output Property is not allowed."); + } + + if (prop_name == "xformOpOrder") { + // To token[] + std::vector toks = xform.xformOpOrder(); + value::Value val(toks); + primvar::PrimVar pvar; + pvar.set_value(toks); + + Attribute attr; + attr.set_var(std::move(pvar)); + attr.variability() = Variability::Uniform; + Property prop; + prop.set_attribute(attr); + + (*out_prop) = prop; + + } else { + // XformOp? + for (const auto &item : xform.xformOps) { + std::string op_name = to_string(item.op_type); + if (item.suffix.size()) { + op_name += ":" + item.suffix; + } + + if (op_name == prop_name) { + return XformOpToProperty(item, *out_prop); + } + } + + const auto it = xform.props.find(prop_name); + if (it == xform.props.end()) { + // Attribute not found. + return false; + } + + (*out_prop) = it->second; + } + + return true; +} + +template <> +nonstd::expected GetPrimProperty( + const GeomMesh &mesh, const std::string &prop_name, Property *out_prop) { + if (!out_prop) { + return nonstd::make_unexpected( + "[InternalError] nullptr in output Property is not allowed."); + } + + DCOUT("prop_name = " << prop_name); + std::string err; + + TO_PROPERTY("points", mesh.points) + TO_PROPERTY("faceVertexCounts", mesh.faceVertexCounts) + TO_PROPERTY("faceVertexIndices", mesh.faceVertexCounts) + TO_PROPERTY("normals", mesh.normals) + TO_PROPERTY("velocities", mesh.velocities) + TO_PROPERTY("cornerIndices", mesh.cornerIndices) + TO_PROPERTY("cornerSharpnesses", mesh.cornerSharpnesses) + TO_PROPERTY("creaseIndices", mesh.creaseIndices) + TO_PROPERTY("creaseSharpnesses", mesh.creaseSharpnesses) + TO_PROPERTY("holeIndices", mesh.holeIndices) + TO_TOKEN_PROPERTY("interpolateBoundary", mesh.interpolateBoundary) + TO_TOKEN_PROPERTY("subdivisionScheme", mesh.subdivisionScheme) + TO_TOKEN_PROPERTY("faceVaryingLinearInterpolation", mesh.faceVaryingLinearInterpolation) + + if (prop_name == "skeleton") { + if (mesh.skeleton) { + const Relationship &rel = mesh.skeleton.value(); + (*out_prop) = Property(rel, /* custom */ false); + } else { + // empty + return false; + } + } else { + const auto it = mesh.props.find(prop_name); + if (it == mesh.props.end()) { + // Attribute not found. + return false; + } + + (*out_prop) = it->second; + } + + DCOUT("Prop found: " << prop_name + << ", ty = " << out_prop->value_type_name()); + return true; +} + +template <> +nonstd::expected GetPrimProperty( + const GeomSubset &subset, const std::string &prop_name, + Property *out_prop) { + if (!out_prop) { + return nonstd::make_unexpected( + "[InternalError] nullptr in output Property is not allowed."); + } + + // Currently GeomSubset does not support TimeSamples and AttributeMeta + + std::string err; + + DCOUT("prop_name = " << prop_name); + TO_PROPERTY("indices", subset.indices); + TO_TOKEN_PROPERTY("elementType", subset.elementType); + //TO_TOKEN_PROPERTY("familyType", subset.familyType); + TO_PROPERTY("familyName", subset.familyName); + + if (prop_name == "material:binding") { + if (subset.materialBinding) { + const Relationship &rel = subset.materialBinding.value(); + (*out_prop) = Property(rel, /* custom */false); + } else { + return false; + } + } else { + const auto it = subset.props.find(prop_name); + if (it == subset.props.end()) { + // Attribute not found. + return false; + } + + (*out_prop) = it->second; + } + + DCOUT("Prop found: " << prop_name + << ", ty = " << out_prop->value_type_name()); + return true; +} + +template <> +nonstd::expected GetPrimProperty( + const UsdUVTexture &tex, const std::string &prop_name, Property *out_prop) { + if (!out_prop) { + return nonstd::make_unexpected( + "[InternalError] nullptr in output Property is not allowed."); + } + + DCOUT("prop_name = " << prop_name); + std::string err; + + TO_PROPERTY("inputs:file", tex.file) + + { + const auto it = tex.props.find(prop_name); + if (it == tex.props.end()) { + // Attribute not found. + return false; + } + + (*out_prop) = it->second; + } + + return true; +} + +template <> +nonstd::expected GetPrimProperty( + const UsdPrimvarReader_float2 &preader, const std::string &prop_name, + Property *out_prop) { + if (!out_prop) { + return nonstd::make_unexpected( + "[InternalError] nullptr in output Property is not allowed."); + } + + DCOUT("prop_name = " << prop_name); + std::string err; + + TO_PROPERTY("inputs:varname", preader.varname) + { + const auto it = preader.props.find(prop_name); + if (it == preader.props.end()) { + // Attribute not found. + return false; + } + + (*out_prop) = it->second; + } + DCOUT("prop_name found = " << prop_name); + + return true; +} + +template <> +nonstd::expected GetPrimProperty( + const UsdPrimvarReader_float3 &preader, const std::string &prop_name, + Property *out_prop) { + if (!out_prop) { + return nonstd::make_unexpected( + "[InternalError] nullptr in output Property is not allowed."); + } + + DCOUT("prop_name = " << prop_name); + std::string err; + + TO_PROPERTY("inputs:varname", preader.varname) + + { + const auto it = preader.props.find(prop_name); + if (it == preader.props.end()) { + // Attribute not found. + return false; + } + + (*out_prop) = it->second; + } + + return true; +} + +template <> +nonstd::expected GetPrimProperty( + const UsdPrimvarReader_float4 &preader, const std::string &prop_name, + Property *out_prop) { + if (!out_prop) { + return nonstd::make_unexpected( + "[InternalError] nullptr in output Property is not allowed."); + } + + DCOUT("prop_name = " << prop_name); + std::string err; + + TO_PROPERTY("inputs:varname", preader.varname) + + { + const auto it = preader.props.find(prop_name); + if (it == preader.props.end()) { + // Attribute not found. + return false; + } + + (*out_prop) = it->second; + } + + return true; +} + +template <> +nonstd::expected GetPrimProperty( + const UsdPrimvarReader_float &preader, const std::string &prop_name, + Property *out_prop) { + if (!out_prop) { + return nonstd::make_unexpected( + "[InternalError] nullptr in output Property is not allowed."); + } + + DCOUT("prop_name = " << prop_name); + + std::string err; + + TO_PROPERTY("inputs:varname", preader.varname) + + { + const auto it = preader.props.find(prop_name); + if (it == preader.props.end()) { + // Attribute not found. + return false; + } + + (*out_prop) = it->second; + } + + return true; +} + +template <> +nonstd::expected GetPrimProperty( + const UsdTransform2d &tx, const std::string &prop_name, + Property *out_prop) { + if (!out_prop) { + return nonstd::make_unexpected( + "[InternalError] nullptr in output Property is not allowed."); + } + + DCOUT("prop_name = " << prop_name); + std::string err; + + TO_PROPERTY("rotation", tx.rotation) + TO_PROPERTY("scale", tx.scale) + TO_PROPERTY("translation", tx.translation) + + if (prop_name == "outputs:result") { + // Terminal attribute + if (!tx.result.authored()) { + // not authored + return false; + } + + // empty. type info only + std::string typeName = tx.result.has_actual_type() ? tx.result.get_actual_type_name() : tx.result.type_name(); + (*out_prop) = Property::MakeEmptyAttrib(typeName, /* custom */ false); + } else { + const auto it = tx.props.find(prop_name); + if (it == tx.props.end()) { + // Attribute not found. + return false; + } + + (*out_prop) = it->second; + } + + return true; +} + +template <> +nonstd::expected GetPrimProperty( + const UsdPreviewSurface &surface, const std::string &prop_name, + Property *out_prop) { + if (!out_prop) { + return nonstd::make_unexpected( + "[InternalError] nullptr in output Property is not allowed."); + } + + DCOUT("prop_name = " << prop_name); + std::string err; + + TO_PROPERTY("diffuseColor", surface.diffuseColor) + TO_PROPERTY("emissiveColor", surface.emissiveColor) + TO_PROPERTY("specularColor", surface.specularColor) + TO_PROPERTY("useSpecularWorkflow", surface.useSpecularWorkflow) + TO_PROPERTY("metallic", surface.metallic) + TO_PROPERTY("clearcoat", surface.clearcoat) + TO_PROPERTY("clearcoatRoughness", surface.clearcoatRoughness) + TO_PROPERTY("roughness", surface.roughness) + TO_PROPERTY("opacity", surface.opacity) + TO_PROPERTY("opacityThreshold", surface.opacityThreshold) + TO_PROPERTY("ior", surface.ior) + TO_PROPERTY("normal", surface.normal) + TO_PROPERTY("displacement", surface.displacement) + TO_PROPERTY("occlusion", surface.occlusion) + + if (prop_name == "outputs:surface") { + if (surface.outputsSurface.authored()) { + // empty. type info only + (*out_prop) = Property::MakeEmptyAttrib(value::kToken, /* custom */ false); + } else { + // Not authored + return false; + } + } else if (prop_name == "outputs:displacement") { + if (surface.outputsDisplacement.authored()) { + // empty. type info only + (*out_prop) = Property::MakeEmptyAttrib(value::kToken, /* custom */ false); + } else { + // Not authored + return false; + } + } else { + const auto it = surface.props.find(prop_name); + if (it == surface.props.end()) { + // Attribute not found. + // TODO: report warn? + return false; + } + + (*out_prop) = it->second; + } + + DCOUT("Prop found: " << prop_name + << ", ty = " << out_prop->value_type_name()); + return true; +} + +template <> +nonstd::expected GetPrimProperty( + const Material &material, const std::string &prop_name, + Property *out_prop) { + if (!out_prop) { + return nonstd::make_unexpected( + "[InternalError] nullptr in output Property is not allowed."); + } + + DCOUT("prop_name = " << prop_name); + if (prop_name == "outputs:surface") { + if (material.surface.authored()) { + Attribute attr; + attr.set_type_name(value::TypeTraits::type_name()); + attr.set_connections(material.surface.get_connections()); + attr.metas() = material.surface.metas(); + (*out_prop) = + Property(attr, /* custom */ false); + out_prop->set_listedit_qual(material.surface.get_listedit_qual()); + } else { + // Not authored + return false; + } + } else if (prop_name == "outputs:displacement") { + if (material.displacement.authored()) { + Attribute attr; + attr.set_type_name(value::TypeTraits::type_name()); + attr.set_connections(material.displacement.get_connections()); + attr.metas() = material.displacement.metas(); + (*out_prop) = + Property(attr, /* custom */ false); + out_prop->set_listedit_qual(material.displacement.get_listedit_qual()); + } else { + // Not authored + return false; + } + } else if (prop_name == "outputs:volume") { + if (material.volume.authored()) { + Attribute attr; + attr.set_type_name(value::TypeTraits::type_name()); + attr.set_connections(material.volume.get_connections()); + attr.metas() = material.volume.metas(); + (*out_prop) = + Property(attr, /* custom */ false); + out_prop->set_listedit_qual(material.volume.get_listedit_qual()); + } else { + // Not authored + return false; + } + } else { + const auto it = material.props.find(prop_name); + if (it == material.props.end()) { + // Attribute not found. + return false; + } + + (*out_prop) = it->second; + } + + DCOUT("Prop found: " << prop_name + << ", ty = " << out_prop->value_type_name()); + return true; +} + +template <> +nonstd::expected GetPrimProperty( + const SkelRoot &skelroot, const std::string &prop_name, + Property *out_prop) { + if (!out_prop) { + return nonstd::make_unexpected( + "[InternalError] nullptr in output Property is not allowed."); + } + + DCOUT("prop_name = " << prop_name); + { + const auto it = skelroot.props.find(prop_name); + if (it == skelroot.props.end()) { + // Attribute not found. + return false; + } + + (*out_prop) = it->second; + } + DCOUT("Prop found: " << prop_name + << ", ty = " << out_prop->value_type_name()); + + return true; +} + +template <> +nonstd::expected GetPrimProperty( + const BlendShape &blendshape, const std::string &prop_name, + Property *out_prop) { + if (!out_prop) { + return nonstd::make_unexpected( + "[InternalError] nullptr in output Property is not allowed."); + } + + DCOUT("prop_name = " << prop_name); + std::string err; + + TO_PROPERTY("offsets", blendshape.offsets) + TO_PROPERTY("normalOffsets", blendshape.normalOffsets) + TO_PROPERTY("pointIndices", blendshape.pointIndices) + + { + const auto it = blendshape.props.find(prop_name); + if (it == blendshape.props.end()) { + // Attribute not found. + return false; + } + + (*out_prop) = it->second; + } + DCOUT("Prop found: " << prop_name + << ", ty = " << out_prop->value_type_name()); + return true; +} + +template <> +nonstd::expected GetPrimProperty( + const Skeleton &skel, const std::string &prop_name, Property *out_prop) { + if (!out_prop) { + return nonstd::make_unexpected( + "[InternalError] nullptr in output Property is not allowed."); + } + + DCOUT("prop_name = " << prop_name); + std::string err; + + TO_PROPERTY("bindTransforms", skel.bindTransforms) + TO_PROPERTY("jointNames", skel.jointNames) + TO_PROPERTY("joints", skel.joints) + TO_PROPERTY("restTransforms", skel.restTransforms) + + if (prop_name == "animationSource") { + if (skel.animationSource) { + const Relationship &rel = skel.animationSource.value(); + (*out_prop) = Property(rel, /* custom */ false); + } else { + // empty + return false; + } + } else { + const auto it = skel.props.find(prop_name); + if (it == skel.props.end()) { + // Attribute not found. + return false; + } + + (*out_prop) = it->second; + } + DCOUT("Prop found: " << prop_name + << ", ty = " << out_prop->value_type_name()); + return true; +} + +template <> +nonstd::expected GetPrimProperty( + const SkelAnimation &anim, const std::string &prop_name, + Property *out_prop) { + if (!out_prop) { + return nonstd::make_unexpected( + "[InternalError] nullptr in output Property is not allowed."); + } + + DCOUT("prop_name = " << prop_name); + std::string err; + + TO_PROPERTY("blendShapes", anim.blendShapes) + TO_PROPERTY("blendShapeWeights", anim.blendShapeWeights) + TO_PROPERTY("joints", anim.joints) + TO_PROPERTY("rotations", anim.rotations) + TO_PROPERTY("scales", anim.scales) + TO_PROPERTY("translations", anim.translations) + + { + const auto it = anim.props.find(prop_name); + if (it == anim.props.end()) { + // Attribute not found. + return false; + } + + (*out_prop) = it->second; + } + DCOUT("Prop found: " << prop_name + << ", ty = " << out_prop->value_type_name()); + return true; +} + +template <> +nonstd::expected GetPrimProperty( + const Shader &shader, const std::string &prop_name, Property *out_prop) { + if (!out_prop) { + return nonstd::make_unexpected( + "[InternalError] nullptr in output Property is not allowed."); + } + + if (const auto preader_f = shader.value.as()) { + return GetPrimProperty(*preader_f, prop_name, out_prop); + } else if (const auto preader_f2 = + shader.value.as()) { + return GetPrimProperty(*preader_f2, prop_name, out_prop); + } else if (const auto preader_f3 = + shader.value.as()) { + return GetPrimProperty(*preader_f3, prop_name, out_prop); + } else if (const auto preader_f4 = + shader.value.as()) { + return GetPrimProperty(*preader_f4, prop_name, out_prop); + } else if (const auto ptx2d = shader.value.as()) { + return GetPrimProperty(*ptx2d, prop_name, out_prop); + } else if (const auto ptex = shader.value.as()) { + return GetPrimProperty(*ptex, prop_name, out_prop); + } else if (const auto psurf = shader.value.as()) { + return GetPrimProperty(*psurf, prop_name, out_prop); + } else { + return nonstd::make_unexpected("TODO: " + shader.value.type_name()); + } +} + +template <> +bool GetPrimPropertyNamesImpl( + const Model &model, std::vector *prop_names, bool attr_prop, bool rel_prop) { + + if (!prop_names) { + return false; + } + + // TODO: Use propertyNames() + for (const auto &prop : model.props) { + if (prop.second.is_relationship()) { + if (rel_prop) { + prop_names->push_back(prop.first); + } + } else { // assume attribute + if (attr_prop) { + prop_names->push_back(prop.first); + } + } + } + + return true; +} + +template <> +bool GetPrimPropertyNamesImpl( + const Scope &scope, std::vector *prop_names, bool attr_prop, bool rel_prop) { + if (!prop_names) { + return false; + } + + // TODO: Use propertyNames() + for (const auto &prop : scope.props) { + if (prop.second.is_relationship()) { + if (rel_prop) { + prop_names->push_back(prop.first); + } + } else { // assume attribute + if (attr_prop) { + prop_names->push_back(prop.first); + } + } + } + + return true; +} + +bool GetGPrimPropertyNamesImpl( + const GPrim *gprim, std::vector *prop_names, bool attr_prop, bool rel_prop) { + if (!gprim) { + return false; + } + + if (!prop_names) { + return false; + } + + if (attr_prop) { + + if (gprim->doubleSided.authored()) { + prop_names->push_back("doubleSided"); + } + + if (gprim->orientation.authored()) { + prop_names->push_back("orientation"); + } + + if (gprim->purpose.authored()) { + prop_names->push_back("purpose"); + } + + if (gprim->extent.authored()) { + prop_names->push_back("extent"); + } + + if (gprim->visibility.authored()) { + prop_names->push_back("visibility"); + } + + // xformOps. + for (const auto &xop : gprim->xformOps) { + if (xop.op_type == XformOp::OpType::ResetXformStack) { + // skip + continue; + } + std::string varname = to_string(xop.op_type); + if (!xop.suffix.empty()) { + varname += ":" + xop.suffix; + } + prop_names->push_back(varname); + } + } + + if (rel_prop) { + + if (gprim->materialBinding) { + prop_names->push_back(kMaterialBinding); + } + + if (gprim->materialBindingPreview) { + prop_names->push_back(kMaterialBindingPreview); + } + + if (gprim->materialBindingFull) { + prop_names->push_back(kMaterialBindingFull); + } + + for (const auto &item : gprim->materialBindingMap()) { + prop_names->push_back("material:binding:" + item.first); + } + + for (const auto &collection : gprim->materialBindingCollectionMap()) { + std::string purpose_name; + if (!collection.first.empty()) { + purpose_name = ":" + collection.first; + } + + for (size_t i = 0; i < collection.second.size(); i++) { + const std::string &coll_name = collection.second.keys()[i]; + std::string rel_name; + if (collection.first.empty()) { + rel_name = kMaterialBindingCollection + purpose_name; + } else { + rel_name = kMaterialBindingCollection + std::string(":") + coll_name + purpose_name; + } + + prop_names->push_back(rel_name); + } + } + + if (gprim->proxyPrim.authored()) { + prop_names->push_back("proxyPrim"); + } + + + } + + // other props + for (const auto &prop : gprim->props) { + if (prop.second.is_relationship()) { + if (rel_prop) { + prop_names->push_back(prop.first); + } + } else { // assume attribute + if (attr_prop) { + prop_names->push_back(prop.first); + } + } + } + + return true; +} + +template <> +bool GetPrimPropertyNamesImpl( + const Xform &xform, std::vector *prop_names, bool attr_prop, bool rel_prop) { + if (!prop_names) { + return false; + } + + return GetGPrimPropertyNamesImpl(&xform, prop_names, attr_prop, rel_prop); +} + +template <> +bool GetPrimPropertyNamesImpl( + const GeomMesh &mesh, std::vector *prop_names, bool attr_prop, bool rel_prop) { + if (!prop_names) { + return false; + } + + if (!GetGPrimPropertyNamesImpl(&mesh, prop_names, attr_prop, rel_prop)) { + return false; + } + + if (attr_prop) { + if (mesh.points.authored()) { + prop_names->push_back("points"); + } + + if (mesh.normals.authored()) { + prop_names->push_back("normals"); + } + + DCOUT("TODO: more attrs..."); + } + + return true; +} + +template <> +bool GetPrimPropertyNamesImpl( + const GeomSubset &subset, std::vector *prop_names, bool attr_prop, bool rel_prop) { + + (void)rel_prop; + + if (!prop_names) { + return false; + } + + if (attr_prop) { + if (subset.elementType.authored()) { + prop_names->push_back("elementType"); + } + + if (subset.familyName.authored()) { + prop_names->push_back("familyName"); + } + + if (subset.indices.authored()) { + prop_names->push_back("indices"); + } + + DCOUT("TODO: more attrs..."); + } + + return true; +} + +#undef TO_PROPERTY +#undef TO_TOKEN_PROPERTY + + +} // namespace + +bool VisitPrims(const tinyusdz::Stage &stage, VisitPrimFunction visitor_fun, + void *userdata, std::string *err) { + + // if `primChildren` is available, use it + if (stage.metas().primChildren.size() == stage.root_prims().size()) { + std::map primNameTable; + for (size_t i = 0; i < stage.root_prims().size(); i++) { + primNameTable.emplace(stage.root_prims()[i].element_name(), &stage.root_prims()[i]); + } + + for (size_t i = 0; i < stage.metas().primChildren.size(); i++) { + value::token nameTok = stage.metas().primChildren[i]; + const auto it = primNameTable.find(nameTok.str()); + if (it != primNameTable.end()) { + const Path root_abs_path("/" + nameTok.str(), ""); + if (!VisitPrimsRec(root_abs_path, *it->second, 0, visitor_fun, userdata, err)) { + return false; + } + } else { + if (err) { + (*err) += fmt::format("Prim name `{}` in root Layer's `primChildren` metadatum not found in Layer root.", nameTok.str()); + } + return false; + } + } + + } else { + for (const auto &root : stage.root_prims()) { + const Path root_abs_path("/" + root.element_name(), /* prop part */""); + if (!VisitPrimsRec(root_abs_path, root, /* root level */ 0, visitor_fun, userdata, err)) { + return false; + } + } + } + + return true; +} + +bool GetProperty(const tinyusdz::Prim &prim, const std::string &attr_name, + Property *out_prop, std::string *err) { +#define GET_PRIM_PROPERTY(__ty) \ + if (prim.is<__ty>()) { \ + auto ret = GetPrimProperty(*prim.as<__ty>(), attr_name, out_prop); \ + if (ret) { \ + if (!ret.value()) { \ + PUSH_ERROR_AND_RETURN( \ + fmt::format("Attribute `{}` does not exist in Prim {}({})", \ + attr_name, prim.element_path().prim_part(), \ + value::TypeTraits<__ty>::type_name())); \ + } \ + } else { \ + PUSH_ERROR_AND_RETURN(ret.error()); \ + } \ + } else + + GET_PRIM_PROPERTY(Model) + GET_PRIM_PROPERTY(Xform) + GET_PRIM_PROPERTY(Scope) + GET_PRIM_PROPERTY(GeomMesh) + GET_PRIM_PROPERTY(GeomSubset) + GET_PRIM_PROPERTY(Shader) + GET_PRIM_PROPERTY(Material) + GET_PRIM_PROPERTY(SkelRoot) + GET_PRIM_PROPERTY(BlendShape) + GET_PRIM_PROPERTY(Skeleton) + GET_PRIM_PROPERTY(SkelAnimation) { + PUSH_ERROR_AND_RETURN("TODO: Prim type " << prim.type_name()); + } + +#undef GET_PRIM_PROPERTY + + return true; +} + +bool GetPropertyNames(const tinyusdz::Prim &prim, std::vector *out_prop_names, std::string *err) { +#define GET_PRIM_PROPERTY_NAMES(__ty) \ + if (prim.is<__ty>()) { \ + auto ret = GetPrimPropertyNamesImpl(*prim.as<__ty>(), out_prop_names, true, true); \ + if (!ret) { \ + PUSH_ERROR_AND_RETURN(fmt::format("Failed to list up Property names of Prim type {}", value::TypeTraits<__ty>::type_name())); \ + } \ + } else + + GET_PRIM_PROPERTY_NAMES(Model) + GET_PRIM_PROPERTY_NAMES(Xform) + GET_PRIM_PROPERTY_NAMES(Scope) + GET_PRIM_PROPERTY_NAMES(GeomMesh) + GET_PRIM_PROPERTY_NAMES(GeomSubset) + // TODO + //GET_PRIM_PROPERTY_NAMES(Shader) + //GET_PRIM_PROPERTY_NAMES(Material) + //GET_PRIM_PROPERTY_NAMES(SkelRoot) + //GET_PRIM_PROPERTY_NAMES(BlendShape) + //GET_PRIM_PROPERTY_NAMES(Skeleton) + //GET_PRIM_PROPERTY_NAMES(SkelAnimation) + { + PUSH_ERROR_AND_RETURN("TODO: Prim type " << prim.type_name()); + } + +#undef GET_PRIM_PROPERTY_NAMES + + return true; +} + +bool GetRelationshipNames(const tinyusdz::Prim &prim, std::vector *out_rel_names, std::string *err) { +#define GET_PRIM_RELATIONSHIP_NAMES(__ty) \ + if (prim.is<__ty>()) { \ + auto ret = GetPrimPropertyNamesImpl(*prim.as<__ty>(), out_rel_names, false, true); \ + if (!ret) { \ + PUSH_ERROR_AND_RETURN(fmt::format("Failed to list up Property names of Prim type {}", value::TypeTraits<__ty>::type_name())); \ + } \ + } else + + GET_PRIM_RELATIONSHIP_NAMES(Model) + GET_PRIM_RELATIONSHIP_NAMES(Xform) + GET_PRIM_RELATIONSHIP_NAMES(Scope) + GET_PRIM_RELATIONSHIP_NAMES(GeomMesh) + //GET_PRIM_RELATIONSHIP_NAMES(GeomSubset) + //GET_PRIM_RELATIONSHIP_NAMES(Shader) + //GET_PRIM_RELATIONSHIP_NAMES(Material) + //GET_PRIM_RELATIONSHIP_NAMES(SkelRoot) + //GET_PRIM_RELATIONSHIP_NAMES(BlendShape) + //GET_PRIM_RELATIONSHIP_NAMES(Skeleton) + //GET_PRIM_RELATIONSHIP_NAMES(SkelAnimation) + { + PUSH_ERROR_AND_RETURN("TODO: Prim type " << prim.type_name()); + } + +#undef GET_PRIM_PROPERTY_NAMES + + return true; +} + + +bool GetAttribute(const tinyusdz::Prim &prim, const std::string &attr_name, + Attribute *out_attr, std::string *err) { + if (!out_attr) { + PUSH_ERROR_AND_RETURN("`out_attr` argument is nullptr."); + } + + // First lookup as Property, then check if its Attribute + Property prop; + if (!GetProperty(prim, attr_name, &prop, err)) { + return false; + } + + if (prop.is_attribute()) { + (*out_attr) = std::move(prop.get_attribute()); + return true; + } + + PUSH_ERROR_AND_RETURN(fmt::format("{} is not a Attribute.", attr_name)); +} + +bool GetRelationship(const tinyusdz::Prim &prim, const std::string &rel_name, + Relationship *out_rel, std::string *err) { + if (!out_rel) { + PUSH_ERROR_AND_RETURN("`out_rel` argument is nullptr."); + } + + // First lookup as Property, then check if its Relationship + Property prop; + if (!GetProperty(prim, rel_name, &prop, err)) { + return false; + } + + if (prop.is_relationship()) { + (*out_rel) = std::move(prop.get_relationship()); + } + + PUSH_ERROR_AND_RETURN(fmt::format("{} is not a Relationship.", rel_name)); + + return true; +} + +bool ListSceneNames(const tinyusdz::Prim &root, + std::vector> *sceneNames) { + if (!sceneNames) { + return false; + } + + bool has_sceneLibrary = false; + if (root.metas().kind.has_value()) { + if (root.metas().kind.value() == Kind::SceneLibrary) { + // ok + has_sceneLibrary = true; + } + } + + if (!has_sceneLibrary) { + return false; + } + + for (const Prim &child : root.children()) { + if (!ListSceneNamesRec(child, /* depth */ 0, sceneNames)) { + return false; + } + } + + return true; +} + +namespace { + +bool BuildXformNodeFromStageRec( + const tinyusdz::Stage &stage, + const Path &parent_abs_path, + const Prim *prim, + XformNode *nodeOut, /* out */ + value::matrix4d rootMat, + const double t, const tinyusdz::value::TimeSampleInterpolationType tinterp) { + + if (!nodeOut) { + return false; + } + + XformNode node; + + if (prim->element_name().empty()) { + // TODO: report error + } + + node.element_name = prim->element_name(); + node.absolute_path = parent_abs_path.AppendPrim(prim->element_name()); + node.prim_id = prim->prim_id(); + node.prim = prim; // Assume Prim's address does not change. + + DCOUT(prim->element_name() << ": IsXformablePrim" << IsXformablePrim(*prim)); + + if (IsXformablePrim(*prim)) { + bool resetXformStack{false}; + + value::matrix4d localMat = GetLocalTransform(*prim, &resetXformStack, t, tinterp); + DCOUT("local mat = " << localMat); + + value::matrix4d worldMat = rootMat; + node.has_resetXformStack() = resetXformStack; + + value::matrix4d m; + + if (resetXformStack) { + // Ignore parent Xform. + m = localMat; + } else { + // matrix is row-major, so local first + m = localMat * worldMat; + } + + node.set_parent_world_matrix(rootMat); + node.set_local_matrix(localMat); + node.set_world_matrix(m); + node.has_xform() = true; + } else { + DCOUT("Not xformable"); + node.has_xform() = false; + node.has_resetXformStack() = false; + node.set_parent_world_matrix(rootMat); + node.set_world_matrix(rootMat); + node.set_local_matrix(value::matrix4d::identity()); + } + + for (const auto &childPrim : prim->children()) { + XformNode childNode; + if (!BuildXformNodeFromStageRec(stage, node.absolute_path, &childPrim, &childNode, node.get_world_matrix(), t, tinterp)) { + return false; + } + + childNode.parent = &node; + node.children.emplace_back(std::move(childNode)); + } + + (*nodeOut) = node; + + + return true; +} + +std::string DumpXformNodeRec( + const XformNode &node, + uint32_t indent) +{ + std::stringstream ss; + + ss << pprint::Indent(indent) << "Prim name: " << node.element_name << " PrimID: " << node.prim_id << " (Path " << node.absolute_path << ") Xformable? " << node.has_xform() << " resetXformStack? " << node.has_resetXformStack() << " {\n"; + ss << pprint::Indent(indent+1) << "parent_world: " << node.get_parent_world_matrix() << "\n"; + ss << pprint::Indent(indent+1) << "world: " << node.get_world_matrix() << "\n"; + ss << pprint::Indent(indent+1) << "local: " << node.get_local_matrix() << "\n"; + + for (const auto &child : node.children) { + ss << DumpXformNodeRec(child, indent+1); + } + ss << pprint::Indent(indent+1) << "}\n"; + + return ss.str(); +} + + +} // namespace local + +bool BuildXformNodeFromStage( + const tinyusdz::Stage &stage, + XformNode *rootNode, /* out */ + const double t, const tinyusdz::value::TimeSampleInterpolationType tinterp) { + + if (!rootNode) { + return false; + } + + XformNode stage_root; + stage_root.element_name = ""; // Stage root element name is empty. + stage_root.absolute_path = Path("/", ""); + stage_root.has_xform() = false; + stage_root.parent = nullptr; + stage_root.prim = nullptr; // No prim for stage root. + stage_root.prim_id = -1; + stage_root.has_resetXformStack() = false; + stage_root.set_parent_world_matrix(value::matrix4d::identity()); + stage_root.set_world_matrix(value::matrix4d::identity()); + stage_root.set_local_matrix(value::matrix4d::identity()); + + for (const auto &root : stage.root_prims()) { + XformNode node; + + value::matrix4d rootMat{value::matrix4d::identity()}; + + if (!BuildXformNodeFromStageRec(stage, stage_root.absolute_path, &root, &node, rootMat, t, tinterp)) { + return false; + } + + stage_root.children.emplace_back(std::move(node)); + } + + (*rootNode) = stage_root; + + return true; +} + +std::string DumpXformNode( + const XformNode &node) +{ + return DumpXformNodeRec(node, 0); + +} + +template +bool PrimToPrimSpecImpl(const T &p, PrimSpec &ps, std::string *err); + +template<> +bool PrimToPrimSpecImpl(const Model &p, PrimSpec &ps, std::string *err) { + (void)err; + + ps.name() = p.name; + ps.specifier() = p.spec; + + ps.props() = p.props; + ps.metas() = p.meta; + + // TODO: variantSet + //ps.variantSets + + return true; +} + +template<> +bool PrimToPrimSpecImpl(const Xform &p, PrimSpec &ps, std::string *err) { + (void)err; + + ps.name() = p.name; + ps.specifier() = p.spec; + + ps.props() = p.props; + + // TODO.. + std::vector toks; + Attribute xformOpOrderAttr; + xformOpOrderAttr.set_value(toks); + ps.props().emplace("xformOpOrder", Property(xformOpOrderAttr, /* custom */false)); + + ps.metas() = p.meta; + + // TODO: variantSet + //ps.variantSets + + return true; +} + +bool PrimToPrimSpec(const Prim &prim, PrimSpec &ps, std::string *err) +{ + +#define TO_PRIMSPEC(__ty) \ + if (prim.as<__ty>()) { \ + return PrimToPrimSpecImpl(*(prim.as<__ty>()), ps, err); \ + } else + + + TO_PRIMSPEC(Model) + { + if (err) { + (*err) += "Unsupported/unimplemented Prim type: " + prim.prim_type_name() + "\n"; + } + return false; + } + +#undef TO_PRIMSPEC + +} + +bool ShaderToPrimSpec(const UsdTransform2d &node, PrimSpec &ps, std::string *warn, std::string *err) +{ + (void)warn; + +#define TO_PROPERTY(__prop_name, __v) { \ + Property prop; \ + if (!ToProperty(__v, prop, err)) { \ + PUSH_ERROR_AND_RETURN(fmt::format("Convert {} to Property failed.\n", __prop_name)); \ + } \ + ps.props()[__prop_name] = prop; \ +} + + // inputs + TO_PROPERTY("inputs:in", node.in) + TO_PROPERTY("inputs:rotation", node.rotation) + TO_PROPERTY("inputs:scale", node.scale) + TO_PROPERTY("inputs:translation", node.translation) + + // outputs + if (auto pv = TypedTerminalAttributeToProperty(node.result)) { + ps.props()["outputs:result"] = pv.value(); + } + + for (auto prop : node.props) { + ps.props()[prop.first] = prop.second; + } + + ps.props()[kInfoId] = Property(Attribute::Uniform(value::token(kUsdTransform2d))); + ps.metas() = node.metas(); + ps.name() = node.name; + ps.specifier() = node.spec; + + return true; +} + +std::vector GetGeomSubsets(const tinyusdz::Stage &stage, const tinyusdz::Path &prim_path, const tinyusdz::value::token &familyName, bool prim_must_be_geommesh) { + std::vector result; + + const Prim *pprim{nullptr}; + if (!stage.find_prim_at_path(prim_path, pprim)) { + return result; + } + + if (!pprim) { + return result; + } + + if (prim_must_be_geommesh && !pprim->is()) { + return result; + } + + // Only account for child Prims. + for (const auto &p : pprim->children()) { + if (auto pv = p.as()) { + if (familyName.valid()) { + + if (pv->familyName.authored()) { + if (pv->familyName.get_value().has_value()) { + const value::token &tok = pv->familyName.get_value().value(); + if (familyName.str() == tok.str()) { + result.push_back(pv); + } + } else { + // connection attr or value block? + // skip adding this GeomSubset. + } + } else { + result.push_back(pv); + } + } else { + result.push_back(pv); + } + } + } + + return result; +} + +std::vector GetGeomSubsetChildren(const tinyusdz::Prim &prim, const tinyusdz::value::token &familyName, bool prim_must_be_geommesh) { + std::vector result; + + if (prim_must_be_geommesh && !prim.is()) { + return result; + } + + // Only account for child Prims. + for (const auto &p : prim.children()) { + if (auto pv = p.as()) { + if (familyName.valid()) { + + if (pv->familyName.authored()) { + if (pv->familyName.get_value().has_value()) { + const value::token &tok = pv->familyName.get_value().value(); + if (familyName.str() == tok.str()) { + result.push_back(pv); + } + } else { + // connection attr or value block? + // skip adding this GeomSubset. + } + } else { + result.push_back(pv); + } + } else { + result.push_back(pv); + } + } + } + + return result; +} + +#if 0 +bool ShaderToPrimSpec(const UsdUVTexture &node, PrimSpec &ps, std::string *warn, std::string *err) +{ + (void)warn; + +#define TO_PROPERTY(__prop_name, __v) { \ + Property prop; \ + if (!ToProperty(__v, prop, err)) { \ + PUSH_ERROR_AND_RETURN(fmt::format("Convert {} to Property failed.\n", __prop_name)); \ + } \ + ps.props()[__prop_name] = prop; \ +} + + // inputs + TO_PROPERTY("inputs:in", node.in) + TO_PROPERTY("inputs:rotation", node.rotation) + TO_PROPERTY("inputs:scale", node.scale) + TO_PROPERTY("inputs:translation", node.translation) + + // outputs + if (auto pv = TypedTerminalAttributeToProperty(node.result)) { + ps.props()["outputs:result"] = pv.value(); + } + + for (auto prop : node.props) { + ps.props()[prop.first] = prop.second; + } + + ps.metas() = node.metas(); + ps.name() = node.name; + ps.specifier() = node.spec; + + return true; +} +#endif + +bool GetCollection(const Prim &prim, const Collection **dst) { + if (!dst) { + return false; + } + + auto fn = [dst](const Collection *coll) { + (*dst) = coll; + return true; + }; + + bool ret = ApplyToCollection(prim, fn); + + return ret; +} + +bool IsPathIncluded(const CollectionMembershipQuery &query, const Stage &stage, const Path &abs_path, const CollectionInstance::ExpansionRule expansionRule) { + + (void)query; + (void)stage; + (void)expansionRule; + + DCOUT("TODO"); + + if (!abs_path.is_valid()) { + return false; + } + + if (abs_path.is_root_path()) { + return true; + } + + return false; + +} + +std::vector> +GetBlendShapes( + const tinyusdz::Stage &stage, + const tinyusdz::Prim &prim, std::string *err) { + + std::vector> dst; + + auto *pmesh = prim.as(); + if (!pmesh) { + if (err) { + (*err) += "Prim must be GeomMesh.\n"; + } + return std::vector>{}; + } + + // + // BlendShape Prim may not be a child of GeomMesh. So need to search Prim in + // Stage + // + if (pmesh->blendShapes.authored() && pmesh->blendShapeTargets.has_value()) { + // TODO: connection? + std::vector blendShapeNames; + + if (!pmesh->blendShapes.get_value(&blendShapeNames)) { + if (err) { + (*err) += "Failed to get `skel:blendShapes` attribute.\n"; + } + return std::vector>{}; + } + + if (pmesh->blendShapeTargets.value().is_path()) { + if (blendShapeNames.size() != 1) { + if (err) { + (*err) += "Array size mismatch with `skel:blendShapes` and " + "`skel:blendShapeTargets`.\n"; + } + return std::vector>{}; + } + + const Path &targetPath = pmesh->blendShapeTargets.value().targetPath; + const Prim *bsprim{nullptr}; + if (!stage.find_prim_at_path(targetPath, bsprim, err)) { + return std::vector>{}; + } + if (!bsprim) { + if (err) { (*err) += "Internal error. BlendShape Prim is nullptr.\n"; } + return std::vector>{}; + } + + if (const auto *bs = bsprim->as()) { + dst.push_back(std::make_pair(blendShapeNames[0].str(), bs)); + } else { + if (err) { + (*err) += fmt::format("{} is not BlendShape Prim.\n", + targetPath.full_path_name()); + } + return std::vector>{}; + } + + } else if (pmesh->blendShapeTargets.value().is_pathvector()) { + if (blendShapeNames.size() != + pmesh->blendShapeTargets.value().targetPathVector.size()) { + if (err) { (*err) += + "Array size mismatch with `skel:blendShapes` and " + "`skel:blendShapeTargets`.\n"; } + return std::vector>{}; + } + } else { + if (err) { (*err) += + "Invalid or unsupported definition of `skel:blendShapeTargets` " + "relationship.\n"; } + return std::vector>{}; + } + + for (size_t i = 0; + i < pmesh->blendShapeTargets.value().targetPathVector.size(); i++) { + const Path &targetPath = + pmesh->blendShapeTargets.value().targetPathVector[i]; + const Prim *bsprim{nullptr}; + if (!stage.find_prim_at_path(targetPath, bsprim, err)) { + return std::vector>{}; + } + if (!bsprim) { + if (err) { (*err) += "Internal error. BlendShape Prim is nullptr."; } + return std::vector>{}; + } + + if (const auto *bs = bsprim->as()) { + dst.push_back(std::make_pair(blendShapeNames[0].str(), bs)); + } else { + if (err) { (*err) += fmt::format("{} is not BlendShape Prim.", + targetPath.full_path_name()); } + return std::vector>{}; + } + } + } + + return dst; +} + +bool GetGeomPrimvar(const Stage &stage, const GPrim *gprim, const std::string &varname, GeomPrimvar *out_primvar, std::string *err) { + if (!out_primvar) { + PUSH_ERROR_AND_RETURN("Output GeomPrimvar is nullptr."); + } + + if (!gprim) { + PUSH_ERROR_AND_RETURN("Input `gprim` arg is nullptr."); + } + + GeomPrimvar primvar; + + constexpr auto kPrimvars = "primvars:"; + constexpr auto kIndices = ":indices"; + + std::string primvar_name = kPrimvars + varname; + + const auto it = gprim->props.find(primvar_name); + if (it == gprim->props.end()) { + return false; + } + + if (it->second.is_attribute()) { + const Attribute &attr = it->second.get_attribute(); + + if (attr.is_connection()) { + // follow targetPath to get Attribute + Attribute terminal_attr; + bool ret = tydra::GetTerminalAttribute(stage, attr, primvar_name, &terminal_attr, err); + if (!ret) { + return false; + } + + primvar.set_value(terminal_attr); + + } else { + primvar.set_value(attr); + } + + primvar.set_name(varname); + + if (attr.metas().interpolation.has_value()) { + primvar.set_interpolation(attr.metas().interpolation.value()); + } + if (attr.metas().elementSize.has_value()) { + primvar.set_elementSize(attr.metas().elementSize.value()); + } + // TODO: copy other attribute metas? + + } else { + PUSH_ERROR_AND_RETURN(fmt::format("{} is not Attribute(Maybe Relationship?).", primvar_name)); + } + + // has indices? + std::string index_name = primvar_name + kIndices; + const auto indexIt = gprim->props.find(index_name); + + if (indexIt != gprim->props.end()) { + if (indexIt->second.is_attribute()) { + const Attribute &indexAttr = indexIt->second.get_attribute(); + + if (!(primvar.get_attribute().type_id() & value::TYPE_ID_1D_ARRAY_BIT)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Indexed GeomPrimVar with scalar PrimVar Attribute is not supported. PrimVar name: {}", primvar_name)); + } + + if (indexAttr.is_connection()) { + // follow targetPath to get Attribute + Attribute terminal_indexAttr; + bool ret = tydra::GetTerminalAttribute(stage, indexAttr, index_name, &terminal_indexAttr, err); + if (!ret) { + return false; + } + + if (terminal_indexAttr.is_timesamples()) { + const auto &ts = terminal_indexAttr.get_var().ts_raw(); + TypedTimeSamples> tss; + if (!tss.from_timesamples(ts)) { + PUSH_ERROR_AND_RETURN(fmt::format("Index Attribute seems not an timesamples with int[] type: {}", index_name)); + } + + primvar.set_indices(tss); + } else if (terminal_indexAttr.is_value()) { + + // TODO: Support uint[]? + std::vector indices; + if (!terminal_indexAttr.get_value(&indices)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Index Attribute is not int[] type. Got {}", + indexAttr.type_name())); + } + + primvar.set_indices(indices); + + } + + } else if (indexAttr.is_timesamples()) { + const auto &ts = indexAttr.get_var().ts_raw(); + TypedTimeSamples> tss; + if (!tss.from_timesamples(ts)) { + PUSH_ERROR_AND_RETURN(fmt::format("Index Attribute seems not an timesamples with int[] type: {}", index_name)); + } + + primvar.set_indices(tss); + } else if (indexAttr.is_blocked()) { + // Value blocked. e.g. `float2[] primvars:st:indices = None` + // We can simply skip reading indices. + } else if (indexAttr.is_value()) { + // Check if int[] type. + // TODO: Support uint[]? + std::vector indices; + if (!indexAttr.get_value(&indices)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Index Attribute is not int[] type. Got {}", + indexAttr.type_name())); + } + + + primvar.set_indices(indices); + } else { + PUSH_ERROR_AND_RETURN("[Internal Error] Invalid Index Attribute."); + } + } else { + // indices are optional, so ok to skip it. + } + } + + (*out_primvar) = primvar; + + return true; + + +} + +namespace { + +// +// visited_paths : To prevent circular referencing of attribute connection. +// +bool GetTerminalAttributeImpl( + const tinyusdz::Stage &stage, const tinyusdz::Prim &prim, + const std::string &attr_name, Attribute *value, + std::string *err, std::set &visited_paths) { + + DCOUT("Prim : " << prim.element_path().element_name() << "(" + << prim.type_name() << ") attr_name " << attr_name); + + Property prop; + if (!GetProperty(prim, attr_name, &prop, err)) { + return false; + } + + if (prop.is_connection()) { + // Follow connection target Path(singple targetPath only). + std::vector pv = prop.get_attribute().connections(); + if (pv.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Connection targetPath is empty for Attribute {}.", attr_name)); + } + + if (pv.size() > 1) { + PUSH_ERROR_AND_RETURN( + fmt::format("Multiple targetPaths assigned to .connection.")); + } + + auto target = pv[0]; + + std::string targetPrimPath = target.prim_part(); + std::string targetPrimPropName = target.prop_part(); + DCOUT("connection targetPath : " << target << "(Prim: " << targetPrimPath + << ", Prop: " << targetPrimPropName + << ")"); + + auto targetPrimRet = + stage.GetPrimAtPath(Path(targetPrimPath, /* prop */ "")); + if (targetPrimRet) { + // Follow the connetion + const Prim *targetPrim = targetPrimRet.value(); + + std::string abs_path = target.full_path_name(); + + if (visited_paths.count(abs_path)) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Circular referencing detected. connectionTargetPath = {}", + to_string(target))); + } + visited_paths.insert(abs_path); + + return GetTerminalAttributeImpl(stage, *targetPrim, targetPrimPropName, + value, err, visited_paths); + + } else { + PUSH_ERROR_AND_RETURN(targetPrimRet.error()); + } + } else if (prop.is_relationship()) { + PUSH_ERROR_AND_RETURN( + fmt::format("Property `{}` is a Relation.", attr_name)); + } else if (prop.is_empty()) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Attribute `{}` is a define-only attribute(no value assigned).", + attr_name)); + } else if (prop.is_attribute()) { + + (*value) = prop.get_attribute(); + + } else { + // ??? + PUSH_ERROR_AND_RETURN( + fmt::format("[InternalError] Invalid Attribute `{}`.", attr_name)); + } + + return true; +} + +} // namespace + +bool GetTerminalAttribute( + const tinyusdz::Stage &stage, const tinyusdz::Attribute &attr, + const std::string &attr_name, Attribute *value, + std::string *err) { + + if (!value) { + PUSH_ERROR_AND_RETURN("`value` arg is nullptr."); + } + + std::set visited_paths; + + if (attr.is_connection()) { + + std::vector pv = attr.connections(); + if (pv.empty()) { + PUSH_ERROR_AND_RETURN(fmt::format("Connection targetPath is empty for Attribute {}.", attr_name)); + } + + if (pv.size() > 1) { + PUSH_ERROR_AND_RETURN( + fmt::format("Multiple targetPaths assigned to .connection.")); + } + + auto target = pv[0]; + + std::string targetPrimPath = target.prim_part(); + std::string targetPrimPropName = target.prop_part(); + DCOUT("connection targetPath : " << target << "(Prim: " << targetPrimPath + << ", Prop: " << targetPrimPropName + << ")"); + + auto targetPrimRet = + stage.GetPrimAtPath(Path(targetPrimPath, /* prop */ "")); + if (targetPrimRet) { + // Follow the connetion + const Prim *targetPrim = targetPrimRet.value(); + + std::string abs_path = target.full_path_name(); + + if (visited_paths.count(abs_path)) { + PUSH_ERROR_AND_RETURN(fmt::format( + "Circular referencing detected. connectionTargetPath = {}", + to_string(target))); + } + visited_paths.insert(abs_path); + + return GetTerminalAttributeImpl(stage, *targetPrim, targetPrimPropName, + value, err, visited_paths); + + } else { + PUSH_ERROR_AND_RETURN(targetPrimRet.error()); + } + + } else { + (*value) = attr; + return true; + } + + return false; + +} + + +} // namespace tydra +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/scene-access.hh b/contrib/tinyusdz/tinyusdz_repo/src/tydra/scene-access.hh new file mode 100644 index 000000000..cc6a5cf9c --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/scene-access.hh @@ -0,0 +1,578 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022-Present Light Transport Entertainment, Inc. +// +// Scene access API +// +// NOTE: Tydra API does not use nonstd::optional and nonstd::expected, +// std::functions and other non basic STL feature for easier language bindings. +// +#pragma once + +#include + +#include "prim-type-macros.inc" +#include "prim-types.hh" +#include "stage.hh" +#include "tiny-format.hh" +#include "usdGeom.hh" +#include "usdLux.hh" +#include "usdShade.hh" +#include "usdSkel.hh" +#include "value-type-macros.inc" +#include "value-types.hh" + +namespace tinyusdz { +namespace tydra { + +// key = fully absolute Prim path in string(e.g. "/xform/geom0") +template +using PathPrimMap = std::map; + +// +// value = pair of Shader Prim which contains the Shader type T("info:id") and +// its concrete Shader type(UsdPreviewSurface) +// +template +using PathShaderMap = + std::map>; + +// TODO: extern template to suppress possible `-Wundefined-func-template`? + +/// +/// List Prim of type T from the Stage. +/// Returns false when unsupported/unimplemented Prim type T is given. +/// +template +bool ListPrims(const tinyusdz::Stage &stage, PathPrimMap &m /* output */); + +#define EXTERN_LISTPRIMS(__ty) \ + extern template bool ListPrims(const tinyusdz::Stage &stage, \ + PathPrimMap<__ty> &m); + +APPLY_FUNC_TO_PRIM_TYPES(EXTERN_LISTPRIMS) + +#undef EXTERN_LISTPRIMS + +/// +/// List Shader of shader type T from the Stage. +/// Returns false when unsupported/unimplemented Shader type T is given. +/// TODO: User defined shader type("info:id") +/// +template +bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m /* output */); + +extern template bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m); +extern template bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m); + +extern template bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m); +extern template bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m); +extern template bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m); +extern template bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m); +extern template bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m); +extern template bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m); +extern template bool ListShaders(const tinyusdz::Stage &stage, + PathShaderMap &m); + +/// +/// Get parent Prim from Path. +/// Path must be fully expanded absolute path. +/// +/// Example: Return "/xform" Prim for "/xform/mesh0" path +/// +/// Returns nullptr when the given Path is a root Prim or invalid Path(`err` +/// will be filled when failed). +/// +const Prim *GetParentPrim(const tinyusdz::Stage &stage, + const tinyusdz::Path &path, std::string *err); + +/// +/// Visit Stage and invoke callback functions for each Prim. +/// Can be used for alternative method of Stage::Traverse() in pxrUSD +/// + +/// +/// Use old-style Callback function approach for easier language bindings +/// +/// @param[in] abs_path Prim's absolute path(e.g. "/xform/mesh0") +/// @param[in] prim Prim +/// @param[in] tree_depth Tree depth of this Prim. 0 = root prim. +/// @param[inout] userdata User data. +/// @param[out] error message. +/// +/// @return Usually true. return false + no error message to notify early +/// termination of visiting Prims. +/// +typedef bool (*VisitPrimFunction)(const Path &abs_path, const Prim &prim, + const int32_t tree_depth, void *userdata, + std::string *err); + +/// +/// Visit Prims in Stage. +/// Use `primChildren` metadatum to determine traversal order of Prims if +/// exists(USDC usually contains `primChildren`) Traversal will be failed when +/// no Prim found specified in `primChildren`(if exists) +/// +/// @param[out] err Error message. +/// +bool VisitPrims(const tinyusdz::Stage &stage, VisitPrimFunction visitor_fun, + void *userdata = nullptr, std::string *err = nullptr); + +/// +/// Get Property(Attribute or Relationship) of given Prim by name. +/// Similar to UsdPrim::GetProperty() in pxrUSD. +/// +/// @param[in] prim Prim +/// @param[in] prop_name Property name +/// @param[out] prop Property +/// @param[out] err Error message(filled when returning false) +/// +/// @return true if Property found in given Prim. +/// @return false if Property is not found in given Prim. +/// +bool GetProperty(const tinyusdz::Prim &prim, const std::string &prop_name, + Property *prop, std::string *err); + +/// +/// Get List of Property(Attribute and Relationship) names of given Prim by +/// name. It includes authored builtin Property names. +/// +/// @param[in] prim Prim +/// @param[out] prop_names Property names +/// @param[out] err Error message(filled when returning false) +/// +/// @return true upon success. +/// @return false when something go wrong. +/// +bool GetPropertyNames(const tinyusdz::Prim &prim, + std::vector *prop_names, std::string *err); + +/// +/// Get List of Attribute names of given Prim. +/// It includes authored builtin Attribute names(e.g. "points" for `GeomMesh`). +/// +/// @param[in] prim Prim +/// @param[out] attr_names Attribute names +/// @param[out] err Error message(filled when returning false) +/// +/// @return true upon success. +/// @return false when something go wrong. +/// +bool GetAttributeNames(const tinyusdz::Prim &prim, + std::vector *attr_names, std::string *err); + +/// +/// Get List of Relationship names of given Prim. +/// It includes authored builtin Relationship names(e.g. "proxyPrim" for +/// `GeomMesh`). +/// +/// @param[in] prim Prim +/// @param[out] rel_names Relationship names +/// @param[out] err Error message(filled when returning false) +/// +/// @return true upon success. +/// @return false when something go wrong. +/// +bool GetRelationshipNames(const tinyusdz::Prim &prim, + std::vector *rel_names, + std::string *err); + +/// +/// Get Attribute of given Prim by name. +/// Similar to UsdPrim::GetAttribute() in pxrUSD. +/// +/// @param[in] prim Prim +/// @param[in] attr_name Attribute name +/// @param[out] attr Attribute +/// @param[out] err Error message(filled when returning false) +/// +/// @return true if Attribute found in given Prim. +/// @return false if Attribute is not found in given Prim, or `attr_name` is a +/// Relationship. +/// +bool GetAttribute(const tinyusdz::Prim &prim, const std::string &attr_name, + Attribute *attr, std::string *err); + +/// +/// Check if Prim has Attribute. +/// +/// @param[in] prim Prim +/// @param[in] attr_name Attribute name to query. +/// +/// @return true if `attr_name` Attribute exists in the Prim. +/// +bool HasAttribute(const tinyusdz::Prim &prim, const std::string &attr_name); + +/// +/// Get Relationship of given Prim by name. +/// Similar to UsdPrim::GetRelationship() in pxrUSD. +/// +/// @param[in] prim Prim +/// @param[in] rel_name Relationship name +/// @param[out] rel Relationship +/// @param[out] err Error message(filled when returning false) +/// +/// @return true if Relationship found in given Prim. +/// @return false if Relationship is not found in given Prim, or `rel_name` is a +/// Attribute. +/// +bool GetRelationship(const tinyusdz::Prim &prim, const std::string &rel_name, + Relationship *rel, std::string *err); + +/// +/// Check if Prim has Relationship. +/// +/// @param[in] prim Prim +/// @param[in] rel_name Relationship name to query. +/// +/// @return true if `rel_name` Relationship exists in the Prim. +/// +bool HasRelationship(const tinyusdz::Prim &prim, const std::string &rel_name); + +/// +/// For efficient Xform retrieval from Stage. +/// +/// XformNode's pointer value and hierarchy become invalid when Prim is +/// removed/added from/to Stage. If you change the content of Stage, please +/// rebuild XformNode using BuildXformNodeFromStage() again +/// +/// TODO: Use prim_id and deprecate the pointer to Prim. +/// +struct XformNode { + std::string element_name; // e.g. "geom0" + Path absolute_path; // e.g. "/xform/geom0" + + const Prim *prim{nullptr}; // The pointer to Prim. + int64_t prim_id{-1}; // Prim id(1 or greater for valid Prim ID) + + XformNode *parent{nullptr}; // pointer to parent + std::vector children; + + const value::matrix4d &get_local_matrix() const { return _local_matrix; } + + // world matrix = parent_world_matrix x local_matrix + // Equivalent to GetLocalToWorldMatrix in pxrUSD + // if !resetXformStack! exists in Prim's xformOpOrder, this returns Prim's + // local matrix (clears parent's world matrix) + const value::matrix4d &get_world_matrix() const { return _world_matrix; } + + const value::matrix4d &get_parent_world_matrix() const { + return _parent_world_matrix; + } + + // TODO: accessible only from Friend class? + void set_local_matrix(const value::matrix4d &m) { _local_matrix = m; } + + void set_world_matrix(const value::matrix4d &m) { _world_matrix = m; } + + void set_parent_world_matrix(const value::matrix4d &m) { + _parent_world_matrix = m; + } + + // true: Prim with Xform(e.g. GeomMesh) + // false: Prim with no Xform(e.g. Stage root("/"), Scope, Material, ...) + bool has_xform() const { return _has_xform; } + bool &has_xform() { return _has_xform; } + + bool has_resetXformStack() const { return _has_resetXformStack; } + bool &has_resetXformStack() { return _has_resetXformStack; } + + private: + bool _has_xform{false}; + bool _has_resetXformStack{false}; // !resetXformStack! in xformOps + value::matrix4d _local_matrix{value::matrix4d::identity()}; + value::matrix4d _world_matrix{value::matrix4d::identity()}; + value::matrix4d _parent_world_matrix{value::matrix4d::identity()}; +}; + +/// +/// Build Xform hierachy from Stage. +/// +/// Xform value is evaluated at specified time and timeSample interpolation +/// type. +/// +/// +bool BuildXformNodeFromStage( + const tinyusdz::Stage &stage, XformNode *root, /* out */ + const double t = tinyusdz::value::TimeCode::Default(), + const tinyusdz::value::TimeSampleInterpolationType tinterp = + tinyusdz::value::TimeSampleInterpolationType::Linear); + +std::string DumpXformNode(const XformNode &root); + +/// +/// Get GeomSubset children of the given Prim path +/// +/// The pointer address is valid until Stage's content is unchanged. +/// +/// @param[in] familyName Get GeomSubset having this `familyName`. empty token = +/// return all GeomSubsets. +/// @param[in] prim_must_be_geommesh Prim path must point to GeomMesh Prim. +/// +/// (TODO: Return id of GeomSubset Prim object, instead of the ponter address) +/// +/// @return array of GeomSubset pointers. Empty array when failed or no +/// GeomSubset Prim(with `familyName`) attached to the Prim. +/// +/// +std::vector GetGeomSubsets( + const tinyusdz::Stage &stage, const tinyusdz::Path &prim_path, + const tinyusdz::value::token &familyName, + bool prim_must_be_geommesh = true); + +/// +/// Get GeomSubset children of the given Prim +/// +/// The pointer address is valid until Stage's content is unchanged. +/// +/// @param[in] familyName Get GeomSubset having this `familyName`. empty token = +/// return all GeomSubsets. +/// @param[in] prim_must_be_geommesh Prim must be GeomMesh Prim type. +/// +/// (TODO: Return id of GeomSubset Prim object, instead of the ponter address) +/// +/// @return array of GeomSubset pointers. Empty array when failed or no +/// GeomSubset Prim(with `familyName`) attached to the Prim. +/// +std::vector GetGeomSubsetChildren( + const tinyusdz::Prim &prim, const tinyusdz::value::token &familyName, + bool prim_must_be_geommesh = true); + +// +// Get BlendShape prims in this GeomMesh Prim +// (`skel:blendShapes`, `skel:blendShapeTargets`) +// +std::vector> +GetBlendShapes(const tinyusdz::Stage &stage, const tinyusdz::Prim &prim, + std::string *err = nullptr); + +#if 0 // TODO +/// +/// Get list of GeomSubset PrimSpecs attached to the PrimSpec +/// Prim path must point to GeomMesh PrimSpec. +/// +/// The pointer address is valid until Layer's content is unchanged. +/// +/// (TODO: Return PrimSpec index instead of the ponter address) +/// +std::vector GetGeomSubsetPrimSpecs(const tinyusdz::Layer &layer, const tinyusdz::Path &prim_path); + +std::vector GetGeomSubsetChildren(const tinyusdz::Path &prim_path); +#endif + +/// +/// For composition. Convert Concrete Prim(Xform, GeomMesh, ...) to PrimSpec, +/// generic Prim container. +/// TODO: Move to *core* module? +/// +bool PrimToPrimSpec(const Prim &prim, PrimSpec &ps, std::string *err); + +/// +/// For MaterialX +/// TODO: Move to shader-network.hh? +/// +bool ShaderToPrimSpec(const UsdUVTexture &node, PrimSpec &ps, std::string *warn, + std::string *err); +bool ShaderToPrimSpec(const UsdTransform2d &node, PrimSpec &ps, + std::string *warn, std::string *err); + +template +bool ShaderToPrimSpec(const UsdPrimvarReader &node, PrimSpec &ps, + std::string *warn, std::string *err); + +// +// Utilities and Query for CollectionAPI +// + +/// +/// Get `Collection` object(properties defined in Collection API) from a given +/// Prim. +/// +/// @param[in] prim Prim +/// @param[out] Pointer to the pointer of found Collection. +/// @return true upon success. +/// +bool GetCollection(const Prim &prim, const Collection **collection); + +class CollectionMembershipQuery { + public: + private: + std::map _expansionRuleMap; +}; + +/// +/// Get terminal Attribute. Similar to GetValueProducingAttribute in pxrUSD. +/// +/// On the contrary to EvaluateAttribute, Do not evaluate Attribute value at +/// specified time. +/// +/// - if Attribute is connection, follow its targetPath recursively until +/// encountering non-connection Attribute. +/// - if Attribute is blocked, return Attribute ValueBlock. +/// - if Attribute is timesamples, return TimeSamples Attribute. +/// - if Attribute is scalar, return scalar Attribute. +/// +/// @return true upon success. +bool GetTerminalAttribute(const Stage &stage, const Attribute &attr, + const std::string &attr_name, Attribute *attr_out, + std::string *err); + +template +bool GetTerminalAttribute(const Stage &stage, const TypedAttribute &attr, + const std::string &attr_name, Attribute *attr_out, + std::string *err) { + if (!attr_out) { + return false; + } + + Attribute value; + if (attr.is_connection()) { + Attribute input; + input.set_connections(attr.connections()); + return GetTerminalAttribute(stage, input, attr_name, attr_out, err); + } else if (attr.is_blocked()) { + value.metas() = attr.metas(); + value.variability() = Variability::Uniform; + value.set_type_name(value::TypeTraits::type_name()); + value.set_blocked(true); + (*attr_out) = std::move(value); + return true; + } else if (attr.is_value_empty()) { + value.set_type_name(value::TypeTraits::type_name()); + value.metas() = attr.metas(); + value.variability() = Variability::Uniform; + } else { + value.set_value(attr.get_value()); + value.metas() = attr.metas(); + value.variability() = Variability::Uniform; + } + + (*attr_out) = std::move(value); + return true; +} + +/// +/// Get Geom Primvar. +/// +/// This API supports Connection Attribute(which requires finding Prim of +/// targetPath in Stage). +/// +/// example of Primvar with Connection Attribute: +/// +/// texCoord2f[] primvars:uvs = +/// int[] primvars:uvs:indices.connection = +/// +/// @param[in] stage Stage +/// @param[in] prim The pointer to GPrim. +/// @param[in] name Primvar name(`primvars:` prefix omitted) +/// @param[out] primvar GeomPrimvar output. +/// @param[out] err Error message. +/// +/// @return true upon success. +/// +bool GetGeomPrimvar(const Stage &stage, const GPrim *prim, + const std::string &name, GeomPrimvar *primvar, + std::string *err = nullptr); + +/// +/// Get Primvars in GPrim. +/// +/// This API supports Connection Attribute(which requires finding Prim of +/// targetPath in Stage). +/// +std::vector GetGeomPrimvars(const Stage &stage, const GPrim &prim); + +/// +/// Build Collection Membership +/// +/// It traverse collection paths starting from `seedCollectionInstance` in the +/// Stage. Note: No circular referencing path allowed. +/// +/// @returns CollectionMembershipQuery object. When encountered an error, +/// CollectionMembershipQuery contains empty info(i.e, all query will fail) +/// +CollectionMembershipQuery BuildCollectionMembershipQuery( + const Stage &stage, const CollectionInstance &seedCollectionInstance); + +bool IsPathIncluded(const CollectionMembershipQuery &query, const Stage &stage, + const Path &abs_path, + const CollectionInstance::ExpansionRule expansionRule = + CollectionInstance::ExpansionRule::ExpandPrims); + +// TODO: Layer version +// bool IsPathIncluded(const Layer &layer, const Path &abs_path, const +// CollectionInstance::ExpansionRule expansionRule = +// CollectionInstance::ExpansionRule::ExpandPrims); + +// +// usdSkel +// + +struct SkelNode { + std::string jointElementName; // elementName(leaf node name) of jointPath. + std::string jointPath; // joints in UsdSkel. Relative or Absolute Prim + // path(e.g. "root/head", "/root/head") + std::string jointName; // jointNames in UsdSkel + int jointId{-1}; // jointIndex(array index in UsdSkel joints) + value::matrix4d bindTransform{value::matrix4d::identity()}; + value::matrix4d restTransform{value::matrix4d::identity()}; + int parentNodeIndex{-1}; + + std::vector childNodeIndices; +}; + +class SkelHierarchy { + public: + SkelHierarchy() = default; + + const std::string &name() const { return _name; } + + bool get_root(SkelNode &dst) { + if (_skel_nodes.empty()) { + _err += "SkelNode is Empty\n"; + return false; + } + + dst = _skel_nodes[0]; + return true; + } + + private: + std::string _warm; + std::string _err; + std::string _name; // Skeleleton Prim name + std::vector _skel_nodes; // [0] = root node. +}; + +/// +/// Extract skeleleton info from Skeleton and build skeleton(bone) hierarchy. +/// +bool BuildSkelHierarchy(const Stage &stage, const Skeleton &skel, + SkelHierarchy &dst, std::string *err = nullptr); + +// +// For USDZ AR extensions +// + +/// +/// List up `sceneName` of given Prim's children +/// https://developer.apple.com/documentation/realitykit/usdz-schemas-for-ar +/// +/// Prim's Kind must be `sceneLibrary` +/// @param[out] List of pair of (Is Specifier `over`, sceneName). For `def` +/// Specifier(primary scene), it is set to false. +/// +/// +bool ListSceneNames(const tinyusdz::Prim &root, + std::vector> *sceneNames); + +} // namespace tydra +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/shader-network.cc b/contrib/tinyusdz/tinyusdz_repo/src/tydra/shader-network.cc new file mode 100644 index 000000000..d3a86dd0e --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/shader-network.cc @@ -0,0 +1,456 @@ +#include "shader-network.hh" +#include "prim-apply.hh" + +#include "common-macros.inc" +#include "tiny-format.hh" +#include "prim-types.hh" +#include "usdShade.hh" +#include "pprinter.hh" +#include "prim-pprint.hh" +#include "value-pprint.hh" +#include "stage.hh" +#include "common-macros.inc" +#include "tydra/scene-access.hh" + + +#define PushError(msg) { \ + if (err) { \ + (*err) += msg; \ + } \ +} + +namespace tinyusdz { +namespace tydra { + +namespace { + +// TODO: There are lots of duplicated codes with EvaluateAttribute() +// Use EvaluateAttribute and deprecate EvaluateShaderAttribute? + +bool EvaluateUsdPreviewSurfaceAttribute( + const Stage &stage, + const UsdPreviewSurface &shader, + const std::string &attr_name, + const uint32_t req_type_id, + value::Value &out_val, + std::string *err, + const value::TimeCode timeCode) { + + (void)stage; + + if ((attr_name == "diffuseColor") && (req_type_id == value::TypeTraits::type_id())) { + if (shader.diffuseColor.authored()) { + + } else { + value::color3f col; + if (shader.diffuseColor.get_value().get_scalar(&col)) { + out_val = col; + return true; + } + } + } + + (void)err; + (void)timeCode; + + return false; +} + + +} // namespace local + + +template +bool EvaluateShaderAttribute( + const Stage &stage, + const Shader &shader, const std::string &attr_name, + T * out_val, + std::string *err, + const value::TimeCode timeCode) { + + if (!out_val) { + return false; + } + + uint32_t tyid = value::TypeTraits::type_id(); + value::Value outval; + + bool result = false; + + if (const auto *psurf = shader.value.as()) { + result = EvaluateUsdPreviewSurfaceAttribute(stage, *psurf, attr_name, tyid, outval, err, timeCode); + if (const auto pt = outval.as()) { + (*out_val) = (*pt); + } else { + if (err) { + (*err) += "[InternalError] Type mismatch.\n"; + } + return false; + } + } else { + if (err) { + (*err) += "Unsupported shader type: " + shader.value.type_name() + "\n"; + } + return false; + } + + return result; +} + +#define INSTANCIATE_EVAL_SHADER(__ty) \ +template bool EvaluateShaderAttribute( const Stage &stage, const Shader &shader, const std::string &attr_name, __ty * out_val, std::string *err, const value::TimeCode timeCode) + +INSTANCIATE_EVAL_SHADER(value::token); +INSTANCIATE_EVAL_SHADER(std::string); +INSTANCIATE_EVAL_SHADER(value::half); +INSTANCIATE_EVAL_SHADER(value::half2); +INSTANCIATE_EVAL_SHADER(value::half3); +INSTANCIATE_EVAL_SHADER(value::half4); +INSTANCIATE_EVAL_SHADER(int32_t); +INSTANCIATE_EVAL_SHADER(value::int2); +INSTANCIATE_EVAL_SHADER(value::int3); +INSTANCIATE_EVAL_SHADER(value::int4); +INSTANCIATE_EVAL_SHADER(uint32_t); +INSTANCIATE_EVAL_SHADER(value::uint2); +INSTANCIATE_EVAL_SHADER(value::uint3); +INSTANCIATE_EVAL_SHADER(value::uint4); +INSTANCIATE_EVAL_SHADER(float); +INSTANCIATE_EVAL_SHADER(value::float2); +INSTANCIATE_EVAL_SHADER(value::float3); +INSTANCIATE_EVAL_SHADER(value::float4); +INSTANCIATE_EVAL_SHADER(double); +INSTANCIATE_EVAL_SHADER(value::double2); +INSTANCIATE_EVAL_SHADER(value::double3); +INSTANCIATE_EVAL_SHADER(value::double4); +INSTANCIATE_EVAL_SHADER(value::quath); +INSTANCIATE_EVAL_SHADER(value::quatf); +INSTANCIATE_EVAL_SHADER(value::quatd); +INSTANCIATE_EVAL_SHADER(value::color3h); +INSTANCIATE_EVAL_SHADER(value::color3f); +INSTANCIATE_EVAL_SHADER(value::color3d); +INSTANCIATE_EVAL_SHADER(value::color4h); +INSTANCIATE_EVAL_SHADER(value::color4f); +INSTANCIATE_EVAL_SHADER(value::color4d); +INSTANCIATE_EVAL_SHADER(value::vector3h); +INSTANCIATE_EVAL_SHADER(value::vector3f); +INSTANCIATE_EVAL_SHADER(value::vector3d); +INSTANCIATE_EVAL_SHADER(value::point3h); +INSTANCIATE_EVAL_SHADER(value::point3f); +INSTANCIATE_EVAL_SHADER(value::point3d); +INSTANCIATE_EVAL_SHADER(value::normal3h); +INSTANCIATE_EVAL_SHADER(value::normal3f); +INSTANCIATE_EVAL_SHADER(value::normal3d); +INSTANCIATE_EVAL_SHADER(value::matrix2d); +INSTANCIATE_EVAL_SHADER(value::matrix3d); +INSTANCIATE_EVAL_SHADER(value::matrix4d); + +// instanciations + +namespace { + +bool GetSinglePath(const Relationship &rel, Path *path) { + if (!path) { + return false; + } + + if (rel.is_path()) { + (*path) = rel.targetPath; + return true; + } else if (rel.is_pathvector()) { + if (rel.targetPathVector.size() > 0) { + (*path) = rel.targetPathVector[0]; + return true; + } + } + + return false; +} + +} // namespace local + +bool GetDirectlyBoundMaterial( + const Stage &_stage, + const Prim &prim, + const std::string &purpose, + tinyusdz::Path *materialPath, + const Material **material, + std::string *err) { + + if (!materialPath) { + PUSH_ERROR_AND_RETURN("`materialPath` ptr is null."); + } + + if (!material) { + PUSH_ERROR_AND_RETURN("`material` ptr is null."); + } + + auto apply_fun = [&](const Stage &stage, const MaterialBinding *mb) -> bool { + + Relationship mat_rel; + if (!mb->get_materialBinding(value::token(purpose), &mat_rel)) { + return false; + } + + if (!GetSinglePath(mat_rel, materialPath)) { + std::string binding_name = kMaterialBinding; + if (!purpose.empty()) { + binding_name += ":" + purpose; + } + PUSH_ERROR_AND_RETURN(fmt::format("`{}` must be single targetPath", binding_name)); + } + + const Prim *p{nullptr}; + if (stage.find_prim_at_path(*materialPath, p, err)) { + if (p->is()) { + (*material) = p->as(); + return true; + } else { + (*material) = nullptr; + } + } + + return false; + }; + + bool ret = ApplyToMaterialBinding(_stage, prim, apply_fun); + + return ret; +} + +bool GetDirectlyBoundMaterial( + const Stage &stage, + const Path &abs_path, + const std::string &purpose, + tinyusdz::Path *materialPath, + const Material **material, + std::string *err) { + + const Prim *p{nullptr}; + if (stage.find_prim_at_path(abs_path, p, err)) { + return GetDirectlyBoundMaterial(stage, *p, purpose, materialPath, material, err); + } + + return false; +} + +bool GetDirectCollectionMaterialBinding( + const Stage &_stage, + const Prim &prim, + const std::string &purpose, + tinyusdz::Path *materialPath, + const Material **material, + std::string *err) { + + if (!materialPath) { + PUSH_ERROR_AND_RETURN("`materialPath` ptr is null."); + } + + if (!material) { + PUSH_ERROR_AND_RETURN("`material` ptr is null."); + } + + (void)err; + + auto apply_fun = [&](const Stage &stage, const MaterialBinding *mb) -> bool { + + Relationship mat_rel; + if (!mb->get_materialBinding(value::token(purpose), &mat_rel)) { + return false; + } + + if (!GetSinglePath(mat_rel, materialPath)) { + return false; + } + + const Prim *p; + if (stage.find_prim_at_path(*materialPath, p, err)) { + if (p->is() && (material != nullptr)) { + (*material) = p->as(); + } else { + (*material) = nullptr; + } + } + + return false; + }; + + bool ret = ApplyToMaterialBinding(_stage, prim, apply_fun); + + return ret; +} + +bool DirectBindingStrongerThanDescendants( + const Stage &_stage, + const Prim &prim, + const std::string &purpose) +{ + auto apply_fun = [&](const Stage &stage, const MaterialBinding *mb) -> bool { + + (void)stage; + + Relationship mat_rel; + if (!mb->get_materialBinding(value::token(purpose), &mat_rel)) { + return false; + } + + const value::token strength = mat_rel.metas().bindMaterialAs.value_or(kWeaderThanDescendants); + return strength.str() == kStrongerThanDescendants; + + }; + + bool ret = ApplyToMaterialBinding(_stage, prim, apply_fun); + + return ret; +} + +bool DirectBindingStrongerThanDescendants( + const Stage &stage, + const Path &abs_path, + const std::string &purpose) { + + const Prim *p{nullptr}; + if (stage.find_prim_at_path(abs_path, p)) { + return DirectBindingStrongerThanDescendants(stage, *p, purpose); + } + + return false; +} + +#if 0 // TODO +bool GetBoundMaterial( + const Stage &_stage, + const Prim &prim, + const std::string &purpose, + tinyusdz::Path *materialPath, + const Material **materiand, + std::string *err) { + + if (materialPath == nullptr) { + return false; + } + + if (material == nullptr) { + return false; + } +} +#endif + +bool GetBoundMaterial( + const Stage &_stage, + const Path &abs_path, + const std::string &materialPurpose, + tinyusdz::Path *materialPath, + const Material **material, + std::string *err) { + + if (materialPath == nullptr) { + return false; + } + + if (material == nullptr) { + return false; + } + + std::vector purposes; + if (materialPurpose.empty()) { + purposes.push_back(value::token("")); // all-purpose + } else { + purposes.push_back(value::token(materialPurpose)); + purposes.push_back(value::token("")); // all-purpose + } + + // for purpose : purposes: + // + // boundMaterial = None + // + // for p = prim, p != Root, p = p.GetParent(): + // + // if DirectBindingStrongerThanDescendants(p, purpose) or not boundMaterial: + // + // if dicrectBind = GetDirectlyBoundMaterial(p, purpose): + // + // boundMaterial = directBound + // + // for collBinding : GetCollectionMaterialBindings(p, purpose): + // + // if (collBinding.GetCollection().Contains(prim) and + // collBinding.IsStrongerThanDescendants() or not boundMaterial): + // + // boundMaterial = collBinding.GetMaterial() + // break + // + // + // if boundMaterial: + // return boundMaterial + // + // + // return False(or return default material) + + for (const auto &purpose : purposes) { + + Path currentPath = abs_path; + Path boundMaterialPath; + const Material *boundMaterial{nullptr}; + + // We need to climb up to the root in any case. + // TODO: Cache result. + uint32_t depth = 0; + while (depth < 1024*128) { // to avoid infinite loop. + + if (!currentPath.is_valid() || currentPath.is_root_path()) { + break; + } + + const Prim *prim{nullptr}; + bool ret = _stage.find_prim_at_path(currentPath, prim, err); + if (!ret) { + return false; + } + + if (!boundMaterial || DirectBindingStrongerThanDescendants(_stage, *prim, purpose.str())) { + + std::string _err; + Path directMaterialPath; + const Material *directMaterial{nullptr}; + bool has_directBound = GetDirectlyBoundMaterial(_stage, *prim, purpose.str(), &directMaterialPath, &directMaterial, &_err); + + if (has_directBound && directMaterial) { + + boundMaterial = directMaterial; + boundMaterialPath = directMaterialPath; + + } else if (_err.size()) { + if (err) { + (*err) += _err; + } + return false; + } + } + + Path parentPath = currentPath.get_parent_prim_path(); + DCOUT("search parent: " << parentPath.full_path_name()); + + currentPath = parentPath; + + if (currentPath.is_root_prim()) { + // parent is root('/'), so no need to follow the parent path anymore. + break; + } + + depth++; + + // TODO: collection + } + + if (boundMaterial) { + (*material) = boundMaterial; + (*materialPath) = boundMaterialPath; + return true; + } + } + + return false; +} + +} // namespace tydra +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/shader-network.hh b/contrib/tinyusdz/tinyusdz_repo/src/tydra/shader-network.hh new file mode 100644 index 000000000..812580d24 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/tydra/shader-network.hh @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - Present, Light Transport Entertainment, Inc. +// +// Shader network evaluation + +#pragma once + +#include + +#include "nonstd/expected.hpp" +#include "value-types.hh" + +namespace tinyusdz { + +// forward decl of prim-types.hh +class Path; +class Stage; +class Prim; +class Layer; +class PrimSpec; + + +// forward decl of usdShade +struct Material; +struct Shader; +enum class MaterialBindingStrength; + +template +struct UsdPrimvarReader; + +using UsdPrimvarReader_float = UsdPrimvarReader; +using UsdPrimvarReader_float2 = UsdPrimvarReader; +using UsdPrimvarReader_float3 = UsdPrimvarReader; +using UsdPrimvarReader_float4 = UsdPrimvarReader; + +namespace tydra { + +// GLSL like data types +using vec2 = value::float2; +using vec3 = value::float3; +using vec4 = value::float4; +using mat2 = value::matrix2f; // float precision + + +/// +/// Evaluate and return COPIED terminal value of the shader attribute +/// +/// If specified attribute has a value(including timeSamples value), returns the value of it. +/// If specified attribute is a connection, it follows the value producing Attribute and returns the value of it. +/// +/// Since the type of shader connection is known in advance, we return the value by template type T, not by `value::Value` +/// NOTE: The returned value is COPIED. This should be OK for Shader network since usually it does not hold large data... +/// +/// @param[in] stage Stage +/// @param[in] shader Shader node in `stage`. +/// @param[in] attr_name Attribute name +/// @param[out] out_val Output evaluated value. +/// @param[out] err Error message +/// @param[in] timeCode (Optional) Evaluate the value at specified time(for timeSampled value) +/// +/// @return true when specified `prop_name` exists in the given `shader` and can resolve the connection and retrieve "terminal" value. +/// +template +bool EvaluateShaderAttribute( + const Stage &stage, + const Shader &shader, const std::string &attr_name, + T * out_val, + std::string *err, + const value::TimeCode timeCode = value::TimeCode::Default()); + +extern template bool EvaluateShaderAttribute(const Stage &stage, const Shader &shader, const std::string &attr_name, value::token * out_val, std::string *err, const value::TimeCode timeCode); + +// Currently float2 only +//std::vector ExtractPrimvarReadersFromMaterialNode(const Prim &node); + + +/// +/// Return true when... +/// +/// - `bindMaterialAs` attribute metadata is "strongerThanDescendants" +/// +bool DirectBindingStrongerThanDescendants( + const Stage &stage, + const Prim &prim, + const std::string &purpose); + +bool DirectBindingStrongerThanDescendants( + const Stage &stage, + const Path &abs_path, + const std::string &purpose); + +/// +/// Get material:binding target Path of given Prim. +/// +/// This API walk up Prim tree to the root and take into account 'material:binding' and 'material:binding:collection'. +/// +/// https://openusd.org/release/wp_usdshade.html#material-resolve-determining-the-bound-material-for-any-geometry-prim +/// +/// @param[in] stage Prim +/// @param[in] prim Prim +/// @param[in] purpose. (Empty string is treated as "all-purpose") +/// @param[out] materialPath Found Material target Path. +/// @param[out] material THe pointer to found Material object in Stage(if no Material object found in Stage, returns nullptr) +/// @return true when bound Material Path is found. +/// + +#if 0 // TODO +bool GetBoundMaterial( + const Stage &stage, + const Prim &prim, + const std::string &purpose, + tinyusdz::Path *materialPath, + const Material **material, + std::string *err); +#endif + +/// +/// `Path` version of `GetBoundMaterial` +/// +bool GetBoundMaterial( + const Stage &stage, + const Path &abs_path, + const std::string &purpose, + tinyusdz::Path *materialPath, + const Material **material, + std::string *err); + +/// +/// Get material:binding target Path of given Prim. +/// +/// This API look into `material:binding` relationship of given Prim only, +/// and do not account for parent's `material:binding`. +/// Also, this API does not look into Material Binding Collection relationships(`material:binding:collection`) +/// +/// @param[in] stage Prim +/// @param[in] prim Prim +/// @param[in] purpose. (Empty string is treated as "all-purpose") +/// @param[out] materialPath Found Material target Path. +/// @param[out] material THe pointer to found Material object in Stage(if no Material object found in Stage, returns nullptr) +/// @return true when bound Material Path is found. +/// +bool GetDirectlyBoundMaterial( + const Stage &stage, + const Prim &prim, + const std::string &purpose, + tinyusdz::Path *materialPath, + const Material **material, + std::string *err); + + +/// +/// `Path` version of `GetDirectlyBoundMaterial` +/// +bool GetDirectlyBoundMaterial( + const Stage &stage, + const Path &abs_path, + const std::string &purpose, + tinyusdz::Path *materialPath, + const Material **material, + std::string *err); + +/// +/// Layer + PrimSpec version +/// +bool GetDirectlyBoundMaterial( + const Layer& layer, + const PrimSpec &ps, + const std::string &purpose, + tinyusdz::Path *materialPath, + const Material **material, + std::string *err); + +/// +/// Layer + Path version +/// +bool GetDirectlyBoundMaterial( + const Layer& layer, + const Path &abs_path, + const std::string &purpose, + tinyusdz::Path *materialPath, + const Material **material, + std::string *err); + + +/// +/// Get material:binding:collection target Path of given Prim. +/// +/// This API look into `material:binding:collection` relationship of given Prim only, +/// and do not account for parent's `material:binding:collection`. +/// Also, this API does not look into Material Binding relationship(`material:binding`) +/// +/// @param[in] stage Prim +/// @param[in] prim Prim +/// @param[in] purpose. (Empty string is treated as "all-purpose") +/// @param[out] materialPath Found Material target Path. +/// @param[out] material THe pointer to found Material object in Stage(if no Material object found in Stage, returns nullptr) +/// @return true when bound Material Path is found. +/// +bool GetDirectCollectionMaterialBinding( + const Stage &stage, + const Prim &prim, + const std::string &purpose, + tinyusdz::Path *materialPath, + const Material **material, + std::string *err); + +/// +/// `Path` version of `GetDirectCollectionMaterialBinding` +/// +bool GetDirectCollectionMaterialBinding( + const Stage &stage, + const Path &abs_path, + const std::string &purpose, + tinyusdz::Path *materialPath, + const Material **material, + std::string *err); + +} // namespace tydra +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/texture-loader.hh b/contrib/tinyusdz/tinyusdz_repo/src/tydra/texture-loader.hh new file mode 100644 index 000000000..e69de29bb diff --git a/contrib/tinyusdz/tinyusdz_repo/src/tydra/tydra.png b/contrib/tinyusdz/tinyusdz_repo/src/tydra/tydra.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec6835d24a72756c5ecbbee9a4bbb512992eca1 GIT binary patch literal 507383 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 bZvpA{D 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

mIf+m|POhvbYq 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

fwzk4pW3+VG4C)bC2z$l)Q0EGU0u3+w> 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 y$!0fKBp1!$z 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

;EaA*K%tgfBS_0-GB9S$Gz6< zU8t#Z@HrRfoj7Bn{pLUYC-LsvvR7i$OS2)gCTxtTAOIQDYa?T%iD6q?1!=YQRQRud zd;4{IP&no{j$ycX@;yZrsmeOC3o8n<`@`z~#1vAtLkhm!~+yeOXWU_ly|FfK0u}a6G(xdFri{ z+-k1Yn!`9Oc~9G}BE7>jM?`F8dp)_|E|+1N(b94`AEq(yDITWfdP7i)+)8EQE+oc- zV`F@&t>kR2M{CcY&n^a0wcaXf-4lp}=-2CQoT4#7M1%J(_$JjD$2S+XF*U= zVuOaYODoA5%g!$6g`GzNBw(Um#fMO<0E%?gR~#&&inP|8;Sl29^5c9$HN4p4jR z&KiS=42nX4hB&3fuZB{G2(+c$JHM>kFb3&0h5!oNvN9?3W|^E10IYyT$~267+6^(Z zt}4A&;r{acL-gZ3Pj%Ubd72LQ3Q(oCt-svP(;Nk~rbW`$r8lWIxZ8Ezw{)B**5-!z z@^tkC((*74>`dNDE{zZ$-=2DHij>x3d?>XNE33|j>2@iPA3ogHg-FIR)T-0rZcmMg z1^%d4vLO&aS~qJQh(d3MNlBIa`O6>3bjcfg*P2+e)9GN5_q~E@&9H7O8G{6QOL2_R zdw2~n*J>lNqnDRfN}b}6_YThGeXlA|+Be_6T`vWR?J(5Z(|YT*UiMon+dPiOT2Sq( zAffG(cZm!$zz0G;| zacZS$Yp9x1MnoUu%WXLx=5h4Fxtvn3V59G?sX)yeGcz#&radVrNbi#V^Z(;NTi3m% zCdkCPElcekz!;+5>gjkg#-x2i!rU4d^3JczW{p)5L8>A|;A32u1VDyJgi0>Q!@O;K z$KLi*T0I_)0stcWwuWikaw8(^jFfu1KXrkea*TY$<^TM@`7nSQ_R_$*fJ|MQA&}OP zv6ae(YfaKB8>=D!U7>Xal(xM*{qS%8RefGBYy0wLGZ?y0%Wi^dQDb-687*Kv-w~-#KsRnGsX~z(HKU9iqLaa1t4=iug3DWW$zstx-BUVD5}nS zWTUO`TQSxmkuhuxSu>EuUgiBaZ?4ZvN*M@p?GJZv0b1G0-Ram{7k~`w?>>F{)1UkZ zI{o=S`)A+%>i1#tx9gP%eRQYC2V}T>KEL_qtwOJRjq^apuz<1540YYfm{xnK9Z{rL zRoSnXBBCnB@|Ty-&czr;;}~kEOks{v8#Bb<%ev2p(WHCIKD zO9AYpa=GO9Zy!`T_Qpov-rpnZaGamN|NZT}m^?5!6@L8cUHOB0Z&PcC%?7$Zgp&6z zWtwB~-WvJtH{Tr}2DIF6x4Yw=^C9K>_T9LqN&;~-+x4o7M(JfM$H(cHfBn}#|MuJQ zG`@ZJ<4ROt%g;lEy>y^B4zCzQ6Kq*mH*l*kjnNnb2GjNFumARMfBf^GfBN+4o3HK> zixeLMZ+lL+b-Ufh=>&wgFVBx(eQW3O_VjdkIK0#?ZCA!A&c{;fcD=Az%Kq-_50}qh ze*L$&o3hRaEc?D>BCng?@Sn{C|wQ1^Y>2$ zcJJ(0-+sHU3$C}^3v&@|msPgZ*|_6qq3b-&ex5&md>ULRy((G)K|;_9(&I3ywl`nj zefi;w_WIq&Ur&c)DY%v7yx+I&G>?QbO#>essscer4db+2FMGk@s}37VcaeoFey)7|O(baTNC$NA%TU#w;0j3uX8 zKtrR9%t~6J5s&~PsJ;d=jpy_Nu*> kXID_StartTable = { + {0x41, 0x5a}, + {0x61, 0x7a}, + {0xaa, 0xaa}, + {0xb5, 0xb5}, + {0xba, 0xba}, + {0xc0, 0xd6}, + {0xd8, 0xf6}, + {0xf8, 0x2c1}, + {0x2c6, 0x2d1}, + {0x2e0, 0x2e4}, + {0x2ec, 0x2ec}, + {0x2ee, 0x2ee}, + {0x370, 0x374}, + {0x376, 0x377}, + {0x37b, 0x37d}, + {0x37f, 0x37f}, + {0x386, 0x386}, + {0x388, 0x38a}, + {0x38c, 0x38c}, + {0x38e, 0x3a1}, + {0x3a3, 0x3f5}, + {0x3f7, 0x481}, + {0x48a, 0x52f}, + {0x531, 0x556}, + {0x559, 0x559}, + {0x560, 0x588}, + {0x5d0, 0x5ea}, + {0x5ef, 0x5f2}, + {0x620, 0x64a}, + {0x66e, 0x66f}, + {0x671, 0x6d3}, + {0x6d5, 0x6d5}, + {0x6e5, 0x6e6}, + {0x6ee, 0x6ef}, + {0x6fa, 0x6fc}, + {0x6ff, 0x6ff}, + {0x710, 0x710}, + {0x712, 0x72f}, + {0x74d, 0x7a5}, + {0x7b1, 0x7b1}, + {0x7ca, 0x7ea}, + {0x7f4, 0x7f5}, + {0x7fa, 0x7fa}, + {0x800, 0x815}, + {0x81a, 0x81a}, + {0x824, 0x824}, + {0x828, 0x828}, + {0x840, 0x858}, + {0x860, 0x86a}, + {0x870, 0x887}, + {0x889, 0x88e}, + {0x8a0, 0x8c9}, + {0x904, 0x939}, + {0x93d, 0x93d}, + {0x950, 0x950}, + {0x958, 0x961}, + {0x971, 0x980}, + {0x985, 0x98c}, + {0x98f, 0x990}, + {0x993, 0x9a8}, + {0x9aa, 0x9b0}, + {0x9b2, 0x9b2}, + {0x9b6, 0x9b9}, + {0x9bd, 0x9bd}, + {0x9ce, 0x9ce}, + {0x9dc, 0x9dd}, + {0x9df, 0x9e1}, + {0x9f0, 0x9f1}, + {0x9fc, 0x9fc}, + {0xa05, 0xa0a}, + {0xa0f, 0xa10}, + {0xa13, 0xa28}, + {0xa2a, 0xa30}, + {0xa32, 0xa33}, + {0xa35, 0xa36}, + {0xa38, 0xa39}, + {0xa59, 0xa5c}, + {0xa5e, 0xa5e}, + {0xa72, 0xa74}, + {0xa85, 0xa8d}, + {0xa8f, 0xa91}, + {0xa93, 0xaa8}, + {0xaaa, 0xab0}, + {0xab2, 0xab3}, + {0xab5, 0xab9}, + {0xabd, 0xabd}, + {0xad0, 0xad0}, + {0xae0, 0xae1}, + {0xaf9, 0xaf9}, + {0xb05, 0xb0c}, + {0xb0f, 0xb10}, + {0xb13, 0xb28}, + {0xb2a, 0xb30}, + {0xb32, 0xb33}, + {0xb35, 0xb39}, + {0xb3d, 0xb3d}, + {0xb5c, 0xb5d}, + {0xb5f, 0xb61}, + {0xb71, 0xb71}, + {0xb83, 0xb83}, + {0xb85, 0xb8a}, + {0xb8e, 0xb90}, + {0xb92, 0xb95}, + {0xb99, 0xb9a}, + {0xb9c, 0xb9c}, + {0xb9e, 0xb9f}, + {0xba3, 0xba4}, + {0xba8, 0xbaa}, + {0xbae, 0xbb9}, + {0xbd0, 0xbd0}, + {0xc05, 0xc0c}, + {0xc0e, 0xc10}, + {0xc12, 0xc28}, + {0xc2a, 0xc39}, + {0xc3d, 0xc3d}, + {0xc58, 0xc5a}, + {0xc5d, 0xc5d}, + {0xc60, 0xc61}, + {0xc80, 0xc80}, + {0xc85, 0xc8c}, + {0xc8e, 0xc90}, + {0xc92, 0xca8}, + {0xcaa, 0xcb3}, + {0xcb5, 0xcb9}, + {0xcbd, 0xcbd}, + {0xcdd, 0xcde}, + {0xce0, 0xce1}, + {0xcf1, 0xcf2}, + {0xd04, 0xd0c}, + {0xd0e, 0xd10}, + {0xd12, 0xd3a}, + {0xd3d, 0xd3d}, + {0xd4e, 0xd4e}, + {0xd54, 0xd56}, + {0xd5f, 0xd61}, + {0xd7a, 0xd7f}, + {0xd85, 0xd96}, + {0xd9a, 0xdb1}, + {0xdb3, 0xdbb}, + {0xdbd, 0xdbd}, + {0xdc0, 0xdc6}, + {0xe01, 0xe30}, + {0xe32, 0xe32}, + {0xe40, 0xe46}, + {0xe81, 0xe82}, + {0xe84, 0xe84}, + {0xe86, 0xe8a}, + {0xe8c, 0xea3}, + {0xea5, 0xea5}, + {0xea7, 0xeb0}, + {0xeb2, 0xeb2}, + {0xebd, 0xebd}, + {0xec0, 0xec4}, + {0xec6, 0xec6}, + {0xedc, 0xedf}, + {0xf00, 0xf00}, + {0xf40, 0xf47}, + {0xf49, 0xf6c}, + {0xf88, 0xf8c}, + {0x1000, 0x102a}, + {0x103f, 0x103f}, + {0x1050, 0x1055}, + {0x105a, 0x105d}, + {0x1061, 0x1061}, + {0x1065, 0x1066}, + {0x106e, 0x1070}, + {0x1075, 0x1081}, + {0x108e, 0x108e}, + {0x10a0, 0x10c5}, + {0x10c7, 0x10c7}, + {0x10cd, 0x10cd}, + {0x10d0, 0x10fa}, + {0x10fc, 0x1248}, + {0x124a, 0x124d}, + {0x1250, 0x1256}, + {0x1258, 0x1258}, + {0x125a, 0x125d}, + {0x1260, 0x1288}, + {0x128a, 0x128d}, + {0x1290, 0x12b0}, + {0x12b2, 0x12b5}, + {0x12b8, 0x12be}, + {0x12c0, 0x12c0}, + {0x12c2, 0x12c5}, + {0x12c8, 0x12d6}, + {0x12d8, 0x1310}, + {0x1312, 0x1315}, + {0x1318, 0x135a}, + {0x1380, 0x138f}, + {0x13a0, 0x13f5}, + {0x13f8, 0x13fd}, + {0x1401, 0x166c}, + {0x166f, 0x167f}, + {0x1681, 0x169a}, + {0x16a0, 0x16ea}, + {0x16ee, 0x16f8}, + {0x1700, 0x1711}, + {0x171f, 0x1731}, + {0x1740, 0x1751}, + {0x1760, 0x176c}, + {0x176e, 0x1770}, + {0x1780, 0x17b3}, + {0x17d7, 0x17d7}, + {0x17dc, 0x17dc}, + {0x1820, 0x1878}, + {0x1880, 0x18a8}, + {0x18aa, 0x18aa}, + {0x18b0, 0x18f5}, + {0x1900, 0x191e}, + {0x1950, 0x196d}, + {0x1970, 0x1974}, + {0x1980, 0x19ab}, + {0x19b0, 0x19c9}, + {0x1a00, 0x1a16}, + {0x1a20, 0x1a54}, + {0x1aa7, 0x1aa7}, + {0x1b05, 0x1b33}, + {0x1b45, 0x1b4c}, + {0x1b83, 0x1ba0}, + {0x1bae, 0x1baf}, + {0x1bba, 0x1be5}, + {0x1c00, 0x1c23}, + {0x1c4d, 0x1c4f}, + {0x1c5a, 0x1c7d}, + {0x1c80, 0x1c88}, + {0x1c90, 0x1cba}, + {0x1cbd, 0x1cbf}, + {0x1ce9, 0x1cec}, + {0x1cee, 0x1cf3}, + {0x1cf5, 0x1cf6}, + {0x1cfa, 0x1cfa}, + {0x1d00, 0x1dbf}, + {0x1e00, 0x1f15}, + {0x1f18, 0x1f1d}, + {0x1f20, 0x1f45}, + {0x1f48, 0x1f4d}, + {0x1f50, 0x1f57}, + {0x1f59, 0x1f59}, + {0x1f5b, 0x1f5b}, + {0x1f5d, 0x1f5d}, + {0x1f5f, 0x1f7d}, + {0x1f80, 0x1fb4}, + {0x1fb6, 0x1fbc}, + {0x1fbe, 0x1fbe}, + {0x1fc2, 0x1fc4}, + {0x1fc6, 0x1fcc}, + {0x1fd0, 0x1fd3}, + {0x1fd6, 0x1fdb}, + {0x1fe0, 0x1fec}, + {0x1ff2, 0x1ff4}, + {0x1ff6, 0x1ffc}, + {0x2071, 0x2071}, + {0x207f, 0x207f}, + {0x2090, 0x209c}, + {0x2102, 0x2102}, + {0x2107, 0x2107}, + {0x210a, 0x2113}, + {0x2115, 0x2115}, + {0x2118, 0x211d}, + {0x2124, 0x2124}, + {0x2126, 0x2126}, + {0x2128, 0x2128}, + {0x212a, 0x2139}, + {0x213c, 0x213f}, + {0x2145, 0x2149}, + {0x214e, 0x214e}, + {0x2160, 0x2188}, + {0x2c00, 0x2ce4}, + {0x2ceb, 0x2cee}, + {0x2cf2, 0x2cf3}, + {0x2d00, 0x2d25}, + {0x2d27, 0x2d27}, + {0x2d2d, 0x2d2d}, + {0x2d30, 0x2d67}, + {0x2d6f, 0x2d6f}, + {0x2d80, 0x2d96}, + {0x2da0, 0x2da6}, + {0x2da8, 0x2dae}, + {0x2db0, 0x2db6}, + {0x2db8, 0x2dbe}, + {0x2dc0, 0x2dc6}, + {0x2dc8, 0x2dce}, + {0x2dd0, 0x2dd6}, + {0x2dd8, 0x2dde}, + {0x3005, 0x3007}, + {0x3021, 0x3029}, + {0x3031, 0x3035}, + {0x3038, 0x303c}, + {0x3041, 0x3096}, + {0x309d, 0x309f}, + {0x30a1, 0x30fa}, + {0x30fc, 0x30ff}, + {0x3105, 0x312f}, + {0x3131, 0x318e}, + {0x31a0, 0x31bf}, + {0x31f0, 0x31ff}, + {0x3400, 0x4dbf}, + {0x4e00, 0xa48c}, + {0xa4d0, 0xa4fd}, + {0xa500, 0xa60c}, + {0xa610, 0xa61f}, + {0xa62a, 0xa62b}, + {0xa640, 0xa66e}, + {0xa67f, 0xa69d}, + {0xa6a0, 0xa6ef}, + {0xa717, 0xa71f}, + {0xa722, 0xa788}, + {0xa78b, 0xa7ca}, + {0xa7d0, 0xa7d1}, + {0xa7d3, 0xa7d3}, + {0xa7d5, 0xa7d9}, + {0xa7f2, 0xa801}, + {0xa803, 0xa805}, + {0xa807, 0xa80a}, + {0xa80c, 0xa822}, + {0xa840, 0xa873}, + {0xa882, 0xa8b3}, + {0xa8f2, 0xa8f7}, + {0xa8fb, 0xa8fb}, + {0xa8fd, 0xa8fe}, + {0xa90a, 0xa925}, + {0xa930, 0xa946}, + {0xa960, 0xa97c}, + {0xa984, 0xa9b2}, + {0xa9cf, 0xa9cf}, + {0xa9e0, 0xa9e4}, + {0xa9e6, 0xa9ef}, + {0xa9fa, 0xa9fe}, + {0xaa00, 0xaa28}, + {0xaa40, 0xaa42}, + {0xaa44, 0xaa4b}, + {0xaa60, 0xaa76}, + {0xaa7a, 0xaa7a}, + {0xaa7e, 0xaaaf}, + {0xaab1, 0xaab1}, + {0xaab5, 0xaab6}, + {0xaab9, 0xaabd}, + {0xaac0, 0xaac0}, + {0xaac2, 0xaac2}, + {0xaadb, 0xaadd}, + {0xaae0, 0xaaea}, + {0xaaf2, 0xaaf4}, + {0xab01, 0xab06}, + {0xab09, 0xab0e}, + {0xab11, 0xab16}, + {0xab20, 0xab26}, + {0xab28, 0xab2e}, + {0xab30, 0xab5a}, + {0xab5c, 0xab69}, + {0xab70, 0xabe2}, + {0xac00, 0xd7a3}, + {0xd7b0, 0xd7c6}, + {0xd7cb, 0xd7fb}, + {0xf900, 0xfa6d}, + {0xfa70, 0xfad9}, + {0xfb00, 0xfb06}, + {0xfb13, 0xfb17}, + {0xfb1d, 0xfb1d}, + {0xfb1f, 0xfb28}, + {0xfb2a, 0xfb36}, + {0xfb38, 0xfb3c}, + {0xfb3e, 0xfb3e}, + {0xfb40, 0xfb41}, + {0xfb43, 0xfb44}, + {0xfb46, 0xfbb1}, + {0xfbd3, 0xfc5d}, + {0xfc64, 0xfd3d}, + {0xfd50, 0xfd8f}, + {0xfd92, 0xfdc7}, + {0xfdf0, 0xfdf9}, + {0xfe71, 0xfe71}, + {0xfe73, 0xfe73}, + {0xfe77, 0xfe77}, + {0xfe79, 0xfe79}, + {0xfe7b, 0xfe7b}, + {0xfe7d, 0xfe7d}, + {0xfe7f, 0xfefc}, + {0xff21, 0xff3a}, + {0xff41, 0xff5a}, + {0xff66, 0xff9d}, + {0xffa0, 0xffbe}, + {0xffc2, 0xffc7}, + {0xffca, 0xffcf}, + {0xffd2, 0xffd7}, + {0xffda, 0xffdc}, + {0x10000, 0x1000b}, + {0x1000d, 0x10026}, + {0x10028, 0x1003a}, + {0x1003c, 0x1003d}, + {0x1003f, 0x1004d}, + {0x10050, 0x1005d}, + {0x10080, 0x100fa}, + {0x10140, 0x10174}, + {0x10280, 0x1029c}, + {0x102a0, 0x102d0}, + {0x10300, 0x1031f}, + {0x1032d, 0x1034a}, + {0x10350, 0x10375}, + {0x10380, 0x1039d}, + {0x103a0, 0x103c3}, + {0x103c8, 0x103cf}, + {0x103d1, 0x103d5}, + {0x10400, 0x1049d}, + {0x104b0, 0x104d3}, + {0x104d8, 0x104fb}, + {0x10500, 0x10527}, + {0x10530, 0x10563}, + {0x10570, 0x1057a}, + {0x1057c, 0x1058a}, + {0x1058c, 0x10592}, + {0x10594, 0x10595}, + {0x10597, 0x105a1}, + {0x105a3, 0x105b1}, + {0x105b3, 0x105b9}, + {0x105bb, 0x105bc}, + {0x10600, 0x10736}, + {0x10740, 0x10755}, + {0x10760, 0x10767}, + {0x10780, 0x10785}, + {0x10787, 0x107b0}, + {0x107b2, 0x107ba}, + {0x10800, 0x10805}, + {0x10808, 0x10808}, + {0x1080a, 0x10835}, + {0x10837, 0x10838}, + {0x1083c, 0x1083c}, + {0x1083f, 0x10855}, + {0x10860, 0x10876}, + {0x10880, 0x1089e}, + {0x108e0, 0x108f2}, + {0x108f4, 0x108f5}, + {0x10900, 0x10915}, + {0x10920, 0x10939}, + {0x10980, 0x109b7}, + {0x109be, 0x109bf}, + {0x10a00, 0x10a00}, + {0x10a10, 0x10a13}, + {0x10a15, 0x10a17}, + {0x10a19, 0x10a35}, + {0x10a60, 0x10a7c}, + {0x10a80, 0x10a9c}, + {0x10ac0, 0x10ac7}, + {0x10ac9, 0x10ae4}, + {0x10b00, 0x10b35}, + {0x10b40, 0x10b55}, + {0x10b60, 0x10b72}, + {0x10b80, 0x10b91}, + {0x10c00, 0x10c48}, + {0x10c80, 0x10cb2}, + {0x10cc0, 0x10cf2}, + {0x10d00, 0x10d23}, + {0x10e80, 0x10ea9}, + {0x10eb0, 0x10eb1}, + {0x10f00, 0x10f1c}, + {0x10f27, 0x10f27}, + {0x10f30, 0x10f45}, + {0x10f70, 0x10f81}, + {0x10fb0, 0x10fc4}, + {0x10fe0, 0x10ff6}, + {0x11003, 0x11037}, + {0x11071, 0x11072}, + {0x11075, 0x11075}, + {0x11083, 0x110af}, + {0x110d0, 0x110e8}, + {0x11103, 0x11126}, + {0x11144, 0x11144}, + {0x11147, 0x11147}, + {0x11150, 0x11172}, + {0x11176, 0x11176}, + {0x11183, 0x111b2}, + {0x111c1, 0x111c4}, + {0x111da, 0x111da}, + {0x111dc, 0x111dc}, + {0x11200, 0x11211}, + {0x11213, 0x1122b}, + {0x1123f, 0x11240}, + {0x11280, 0x11286}, + {0x11288, 0x11288}, + {0x1128a, 0x1128d}, + {0x1128f, 0x1129d}, + {0x1129f, 0x112a8}, + {0x112b0, 0x112de}, + {0x11305, 0x1130c}, + {0x1130f, 0x11310}, + {0x11313, 0x11328}, + {0x1132a, 0x11330}, + {0x11332, 0x11333}, + {0x11335, 0x11339}, + {0x1133d, 0x1133d}, + {0x11350, 0x11350}, + {0x1135d, 0x11361}, + {0x11400, 0x11434}, + {0x11447, 0x1144a}, + {0x1145f, 0x11461}, + {0x11480, 0x114af}, + {0x114c4, 0x114c5}, + {0x114c7, 0x114c7}, + {0x11580, 0x115ae}, + {0x115d8, 0x115db}, + {0x11600, 0x1162f}, + {0x11644, 0x11644}, + {0x11680, 0x116aa}, + {0x116b8, 0x116b8}, + {0x11700, 0x1171a}, + {0x11740, 0x11746}, + {0x11800, 0x1182b}, + {0x118a0, 0x118df}, + {0x118ff, 0x11906}, + {0x11909, 0x11909}, + {0x1190c, 0x11913}, + {0x11915, 0x11916}, + {0x11918, 0x1192f}, + {0x1193f, 0x1193f}, + {0x11941, 0x11941}, + {0x119a0, 0x119a7}, + {0x119aa, 0x119d0}, + {0x119e1, 0x119e1}, + {0x119e3, 0x119e3}, + {0x11a00, 0x11a00}, + {0x11a0b, 0x11a32}, + {0x11a3a, 0x11a3a}, + {0x11a50, 0x11a50}, + {0x11a5c, 0x11a89}, + {0x11a9d, 0x11a9d}, + {0x11ab0, 0x11af8}, + {0x11c00, 0x11c08}, + {0x11c0a, 0x11c2e}, + {0x11c40, 0x11c40}, + {0x11c72, 0x11c8f}, + {0x11d00, 0x11d06}, + {0x11d08, 0x11d09}, + {0x11d0b, 0x11d30}, + {0x11d46, 0x11d46}, + {0x11d60, 0x11d65}, + {0x11d67, 0x11d68}, + {0x11d6a, 0x11d89}, + {0x11d98, 0x11d98}, + {0x11ee0, 0x11ef2}, + {0x11f02, 0x11f02}, + {0x11f04, 0x11f10}, + {0x11f12, 0x11f33}, + {0x11fb0, 0x11fb0}, + {0x12000, 0x12399}, + {0x12400, 0x1246e}, + {0x12480, 0x12543}, + {0x12f90, 0x12ff0}, + {0x13000, 0x1342f}, + {0x13441, 0x13446}, + {0x14400, 0x14646}, + {0x16800, 0x16a38}, + {0x16a40, 0x16a5e}, + {0x16a70, 0x16abe}, + {0x16ad0, 0x16aed}, + {0x16b00, 0x16b2f}, + {0x16b40, 0x16b43}, + {0x16b63, 0x16b77}, + {0x16b7d, 0x16b8f}, + {0x16e40, 0x16e7f}, + {0x16f00, 0x16f4a}, + {0x16f50, 0x16f50}, + {0x16f93, 0x16f9f}, + {0x16fe0, 0x16fe1}, + {0x16fe3, 0x16fe3}, + {0x17000, 0x187f7}, + {0x18800, 0x18cd5}, + {0x18d00, 0x18d08}, + {0x1aff0, 0x1aff3}, + {0x1aff5, 0x1affb}, + {0x1affd, 0x1affe}, + {0x1b000, 0x1b122}, + {0x1b132, 0x1b132}, + {0x1b150, 0x1b152}, + {0x1b155, 0x1b155}, + {0x1b164, 0x1b167}, + {0x1b170, 0x1b2fb}, + {0x1bc00, 0x1bc6a}, + {0x1bc70, 0x1bc7c}, + {0x1bc80, 0x1bc88}, + {0x1bc90, 0x1bc99}, + {0x1d400, 0x1d454}, + {0x1d456, 0x1d49c}, + {0x1d49e, 0x1d49f}, + {0x1d4a2, 0x1d4a2}, + {0x1d4a5, 0x1d4a6}, + {0x1d4a9, 0x1d4ac}, + {0x1d4ae, 0x1d4b9}, + {0x1d4bb, 0x1d4bb}, + {0x1d4bd, 0x1d4c3}, + {0x1d4c5, 0x1d505}, + {0x1d507, 0x1d50a}, + {0x1d50d, 0x1d514}, + {0x1d516, 0x1d51c}, + {0x1d51e, 0x1d539}, + {0x1d53b, 0x1d53e}, + {0x1d540, 0x1d544}, + {0x1d546, 0x1d546}, + {0x1d54a, 0x1d550}, + {0x1d552, 0x1d6a5}, + {0x1d6a8, 0x1d6c0}, + {0x1d6c2, 0x1d6da}, + {0x1d6dc, 0x1d6fa}, + {0x1d6fc, 0x1d714}, + {0x1d716, 0x1d734}, + {0x1d736, 0x1d74e}, + {0x1d750, 0x1d76e}, + {0x1d770, 0x1d788}, + {0x1d78a, 0x1d7a8}, + {0x1d7aa, 0x1d7c2}, + {0x1d7c4, 0x1d7cb}, + {0x1df00, 0x1df1e}, + {0x1df25, 0x1df2a}, + {0x1e030, 0x1e06d}, + {0x1e100, 0x1e12c}, + {0x1e137, 0x1e13d}, + {0x1e14e, 0x1e14e}, + {0x1e290, 0x1e2ad}, + {0x1e2c0, 0x1e2eb}, + {0x1e4d0, 0x1e4eb}, + {0x1e7e0, 0x1e7e6}, + {0x1e7e8, 0x1e7eb}, + {0x1e7ed, 0x1e7ee}, + {0x1e7f0, 0x1e7fe}, + {0x1e800, 0x1e8c4}, + {0x1e900, 0x1e943}, + {0x1e94b, 0x1e94b}, + {0x1ee00, 0x1ee03}, + {0x1ee05, 0x1ee1f}, + {0x1ee21, 0x1ee22}, + {0x1ee24, 0x1ee24}, + {0x1ee27, 0x1ee27}, + {0x1ee29, 0x1ee32}, + {0x1ee34, 0x1ee37}, + {0x1ee39, 0x1ee39}, + {0x1ee3b, 0x1ee3b}, + {0x1ee42, 0x1ee42}, + {0x1ee47, 0x1ee47}, + {0x1ee49, 0x1ee49}, + {0x1ee4b, 0x1ee4b}, + {0x1ee4d, 0x1ee4f}, + {0x1ee51, 0x1ee52}, + {0x1ee54, 0x1ee54}, + {0x1ee57, 0x1ee57}, + {0x1ee59, 0x1ee59}, + {0x1ee5b, 0x1ee5b}, + {0x1ee5d, 0x1ee5d}, + {0x1ee5f, 0x1ee5f}, + {0x1ee61, 0x1ee62}, + {0x1ee64, 0x1ee64}, + {0x1ee67, 0x1ee6a}, + {0x1ee6c, 0x1ee72}, + {0x1ee74, 0x1ee77}, + {0x1ee79, 0x1ee7c}, + {0x1ee7e, 0x1ee7e}, + {0x1ee80, 0x1ee89}, + {0x1ee8b, 0x1ee9b}, + {0x1eea1, 0x1eea3}, + {0x1eea5, 0x1eea9}, + {0x1eeab, 0x1eebb}, + {0x20000, 0x2a6df}, + {0x2a700, 0x2b739}, + {0x2b740, 0x2b81d}, + {0x2b820, 0x2cea1}, + {0x2ceb0, 0x2ebe0}, + {0x2ebf0, 0x2ee5d}, + {0x2f800, 0x2fa1d}, + {0x30000, 0x3134a}, + {0x31350, 0x323af} +}; + +const std::vector> kXID_ContinueTable = { + {0x30, 0x39}, + {0x41, 0x5a}, + {0x5f, 0x5f}, + {0x61, 0x7a}, + {0xaa, 0xaa}, + {0xb5, 0xb5}, + {0xb7, 0xb7}, + {0xba, 0xba}, + {0xc0, 0xd6}, + {0xd8, 0xf6}, + {0xf8, 0x2c1}, + {0x2c6, 0x2d1}, + {0x2e0, 0x2e4}, + {0x2ec, 0x2ec}, + {0x2ee, 0x2ee}, + {0x300, 0x374}, + {0x376, 0x377}, + {0x37b, 0x37d}, + {0x37f, 0x37f}, + {0x386, 0x38a}, + {0x38c, 0x38c}, + {0x38e, 0x3a1}, + {0x3a3, 0x3f5}, + {0x3f7, 0x481}, + {0x483, 0x487}, + {0x48a, 0x52f}, + {0x531, 0x556}, + {0x559, 0x559}, + {0x560, 0x588}, + {0x591, 0x5bd}, + {0x5bf, 0x5bf}, + {0x5c1, 0x5c2}, + {0x5c4, 0x5c5}, + {0x5c7, 0x5c7}, + {0x5d0, 0x5ea}, + {0x5ef, 0x5f2}, + {0x610, 0x61a}, + {0x620, 0x669}, + {0x66e, 0x6d3}, + {0x6d5, 0x6dc}, + {0x6df, 0x6e8}, + {0x6ea, 0x6fc}, + {0x6ff, 0x6ff}, + {0x710, 0x74a}, + {0x74d, 0x7b1}, + {0x7c0, 0x7f5}, + {0x7fa, 0x7fa}, + {0x7fd, 0x7fd}, + {0x800, 0x82d}, + {0x840, 0x85b}, + {0x860, 0x86a}, + {0x870, 0x887}, + {0x889, 0x88e}, + {0x898, 0x8e1}, + {0x8e3, 0x963}, + {0x966, 0x96f}, + {0x971, 0x983}, + {0x985, 0x98c}, + {0x98f, 0x990}, + {0x993, 0x9a8}, + {0x9aa, 0x9b0}, + {0x9b2, 0x9b2}, + {0x9b6, 0x9b9}, + {0x9bc, 0x9c4}, + {0x9c7, 0x9c8}, + {0x9cb, 0x9ce}, + {0x9d7, 0x9d7}, + {0x9dc, 0x9dd}, + {0x9df, 0x9e3}, + {0x9e6, 0x9f1}, + {0x9fc, 0x9fc}, + {0x9fe, 0x9fe}, + {0xa01, 0xa03}, + {0xa05, 0xa0a}, + {0xa0f, 0xa10}, + {0xa13, 0xa28}, + {0xa2a, 0xa30}, + {0xa32, 0xa33}, + {0xa35, 0xa36}, + {0xa38, 0xa39}, + {0xa3c, 0xa3c}, + {0xa3e, 0xa42}, + {0xa47, 0xa48}, + {0xa4b, 0xa4d}, + {0xa51, 0xa51}, + {0xa59, 0xa5c}, + {0xa5e, 0xa5e}, + {0xa66, 0xa75}, + {0xa81, 0xa83}, + {0xa85, 0xa8d}, + {0xa8f, 0xa91}, + {0xa93, 0xaa8}, + {0xaaa, 0xab0}, + {0xab2, 0xab3}, + {0xab5, 0xab9}, + {0xabc, 0xac5}, + {0xac7, 0xac9}, + {0xacb, 0xacd}, + {0xad0, 0xad0}, + {0xae0, 0xae3}, + {0xae6, 0xaef}, + {0xaf9, 0xaff}, + {0xb01, 0xb03}, + {0xb05, 0xb0c}, + {0xb0f, 0xb10}, + {0xb13, 0xb28}, + {0xb2a, 0xb30}, + {0xb32, 0xb33}, + {0xb35, 0xb39}, + {0xb3c, 0xb44}, + {0xb47, 0xb48}, + {0xb4b, 0xb4d}, + {0xb55, 0xb57}, + {0xb5c, 0xb5d}, + {0xb5f, 0xb63}, + {0xb66, 0xb6f}, + {0xb71, 0xb71}, + {0xb82, 0xb83}, + {0xb85, 0xb8a}, + {0xb8e, 0xb90}, + {0xb92, 0xb95}, + {0xb99, 0xb9a}, + {0xb9c, 0xb9c}, + {0xb9e, 0xb9f}, + {0xba3, 0xba4}, + {0xba8, 0xbaa}, + {0xbae, 0xbb9}, + {0xbbe, 0xbc2}, + {0xbc6, 0xbc8}, + {0xbca, 0xbcd}, + {0xbd0, 0xbd0}, + {0xbd7, 0xbd7}, + {0xbe6, 0xbef}, + {0xc00, 0xc0c}, + {0xc0e, 0xc10}, + {0xc12, 0xc28}, + {0xc2a, 0xc39}, + {0xc3c, 0xc44}, + {0xc46, 0xc48}, + {0xc4a, 0xc4d}, + {0xc55, 0xc56}, + {0xc58, 0xc5a}, + {0xc5d, 0xc5d}, + {0xc60, 0xc63}, + {0xc66, 0xc6f}, + {0xc80, 0xc83}, + {0xc85, 0xc8c}, + {0xc8e, 0xc90}, + {0xc92, 0xca8}, + {0xcaa, 0xcb3}, + {0xcb5, 0xcb9}, + {0xcbc, 0xcc4}, + {0xcc6, 0xcc8}, + {0xcca, 0xccd}, + {0xcd5, 0xcd6}, + {0xcdd, 0xcde}, + {0xce0, 0xce3}, + {0xce6, 0xcef}, + {0xcf1, 0xcf3}, + {0xd00, 0xd0c}, + {0xd0e, 0xd10}, + {0xd12, 0xd44}, + {0xd46, 0xd48}, + {0xd4a, 0xd4e}, + {0xd54, 0xd57}, + {0xd5f, 0xd63}, + {0xd66, 0xd6f}, + {0xd7a, 0xd7f}, + {0xd81, 0xd83}, + {0xd85, 0xd96}, + {0xd9a, 0xdb1}, + {0xdb3, 0xdbb}, + {0xdbd, 0xdbd}, + {0xdc0, 0xdc6}, + {0xdca, 0xdca}, + {0xdcf, 0xdd4}, + {0xdd6, 0xdd6}, + {0xdd8, 0xddf}, + {0xde6, 0xdef}, + {0xdf2, 0xdf3}, + {0xe01, 0xe3a}, + {0xe40, 0xe4e}, + {0xe50, 0xe59}, + {0xe81, 0xe82}, + {0xe84, 0xe84}, + {0xe86, 0xe8a}, + {0xe8c, 0xea3}, + {0xea5, 0xea5}, + {0xea7, 0xebd}, + {0xec0, 0xec4}, + {0xec6, 0xec6}, + {0xec8, 0xece}, + {0xed0, 0xed9}, + {0xedc, 0xedf}, + {0xf00, 0xf00}, + {0xf18, 0xf19}, + {0xf20, 0xf29}, + {0xf35, 0xf35}, + {0xf37, 0xf37}, + {0xf39, 0xf39}, + {0xf3e, 0xf47}, + {0xf49, 0xf6c}, + {0xf71, 0xf84}, + {0xf86, 0xf97}, + {0xf99, 0xfbc}, + {0xfc6, 0xfc6}, + {0x1000, 0x1049}, + {0x1050, 0x109d}, + {0x10a0, 0x10c5}, + {0x10c7, 0x10c7}, + {0x10cd, 0x10cd}, + {0x10d0, 0x10fa}, + {0x10fc, 0x1248}, + {0x124a, 0x124d}, + {0x1250, 0x1256}, + {0x1258, 0x1258}, + {0x125a, 0x125d}, + {0x1260, 0x1288}, + {0x128a, 0x128d}, + {0x1290, 0x12b0}, + {0x12b2, 0x12b5}, + {0x12b8, 0x12be}, + {0x12c0, 0x12c0}, + {0x12c2, 0x12c5}, + {0x12c8, 0x12d6}, + {0x12d8, 0x1310}, + {0x1312, 0x1315}, + {0x1318, 0x135a}, + {0x135d, 0x135f}, + {0x1369, 0x1371}, + {0x1380, 0x138f}, + {0x13a0, 0x13f5}, + {0x13f8, 0x13fd}, + {0x1401, 0x166c}, + {0x166f, 0x167f}, + {0x1681, 0x169a}, + {0x16a0, 0x16ea}, + {0x16ee, 0x16f8}, + {0x1700, 0x1715}, + {0x171f, 0x1734}, + {0x1740, 0x1753}, + {0x1760, 0x176c}, + {0x176e, 0x1770}, + {0x1772, 0x1773}, + {0x1780, 0x17d3}, + {0x17d7, 0x17d7}, + {0x17dc, 0x17dd}, + {0x17e0, 0x17e9}, + {0x180b, 0x180d}, + {0x180f, 0x1819}, + {0x1820, 0x1878}, + {0x1880, 0x18aa}, + {0x18b0, 0x18f5}, + {0x1900, 0x191e}, + {0x1920, 0x192b}, + {0x1930, 0x193b}, + {0x1946, 0x196d}, + {0x1970, 0x1974}, + {0x1980, 0x19ab}, + {0x19b0, 0x19c9}, + {0x19d0, 0x19da}, + {0x1a00, 0x1a1b}, + {0x1a20, 0x1a5e}, + {0x1a60, 0x1a7c}, + {0x1a7f, 0x1a89}, + {0x1a90, 0x1a99}, + {0x1aa7, 0x1aa7}, + {0x1ab0, 0x1abd}, + {0x1abf, 0x1ace}, + {0x1b00, 0x1b4c}, + {0x1b50, 0x1b59}, + {0x1b6b, 0x1b73}, + {0x1b80, 0x1bf3}, + {0x1c00, 0x1c37}, + {0x1c40, 0x1c49}, + {0x1c4d, 0x1c7d}, + {0x1c80, 0x1c88}, + {0x1c90, 0x1cba}, + {0x1cbd, 0x1cbf}, + {0x1cd0, 0x1cd2}, + {0x1cd4, 0x1cfa}, + {0x1d00, 0x1f15}, + {0x1f18, 0x1f1d}, + {0x1f20, 0x1f45}, + {0x1f48, 0x1f4d}, + {0x1f50, 0x1f57}, + {0x1f59, 0x1f59}, + {0x1f5b, 0x1f5b}, + {0x1f5d, 0x1f5d}, + {0x1f5f, 0x1f7d}, + {0x1f80, 0x1fb4}, + {0x1fb6, 0x1fbc}, + {0x1fbe, 0x1fbe}, + {0x1fc2, 0x1fc4}, + {0x1fc6, 0x1fcc}, + {0x1fd0, 0x1fd3}, + {0x1fd6, 0x1fdb}, + {0x1fe0, 0x1fec}, + {0x1ff2, 0x1ff4}, + {0x1ff6, 0x1ffc}, + {0x200c, 0x200d}, + {0x203f, 0x2040}, + {0x2054, 0x2054}, + {0x2071, 0x2071}, + {0x207f, 0x207f}, + {0x2090, 0x209c}, + {0x20d0, 0x20dc}, + {0x20e1, 0x20e1}, + {0x20e5, 0x20f0}, + {0x2102, 0x2102}, + {0x2107, 0x2107}, + {0x210a, 0x2113}, + {0x2115, 0x2115}, + {0x2118, 0x211d}, + {0x2124, 0x2124}, + {0x2126, 0x2126}, + {0x2128, 0x2128}, + {0x212a, 0x2139}, + {0x213c, 0x213f}, + {0x2145, 0x2149}, + {0x214e, 0x214e}, + {0x2160, 0x2188}, + {0x2c00, 0x2ce4}, + {0x2ceb, 0x2cf3}, + {0x2d00, 0x2d25}, + {0x2d27, 0x2d27}, + {0x2d2d, 0x2d2d}, + {0x2d30, 0x2d67}, + {0x2d6f, 0x2d6f}, + {0x2d7f, 0x2d96}, + {0x2da0, 0x2da6}, + {0x2da8, 0x2dae}, + {0x2db0, 0x2db6}, + {0x2db8, 0x2dbe}, + {0x2dc0, 0x2dc6}, + {0x2dc8, 0x2dce}, + {0x2dd0, 0x2dd6}, + {0x2dd8, 0x2dde}, + {0x2de0, 0x2dff}, + {0x3005, 0x3007}, + {0x3021, 0x302f}, + {0x3031, 0x3035}, + {0x3038, 0x303c}, + {0x3041, 0x3096}, + {0x3099, 0x309a}, + {0x309d, 0x309f}, + {0x30a1, 0x30ff}, + {0x3105, 0x312f}, + {0x3131, 0x318e}, + {0x31a0, 0x31bf}, + {0x31f0, 0x31ff}, + {0x3400, 0x4dbf}, + {0x4e00, 0xa48c}, + {0xa4d0, 0xa4fd}, + {0xa500, 0xa60c}, + {0xa610, 0xa62b}, + {0xa640, 0xa66f}, + {0xa674, 0xa67d}, + {0xa67f, 0xa6f1}, + {0xa717, 0xa71f}, + {0xa722, 0xa788}, + {0xa78b, 0xa7ca}, + {0xa7d0, 0xa7d1}, + {0xa7d3, 0xa7d3}, + {0xa7d5, 0xa7d9}, + {0xa7f2, 0xa827}, + {0xa82c, 0xa82c}, + {0xa840, 0xa873}, + {0xa880, 0xa8c5}, + {0xa8d0, 0xa8d9}, + {0xa8e0, 0xa8f7}, + {0xa8fb, 0xa8fb}, + {0xa8fd, 0xa92d}, + {0xa930, 0xa953}, + {0xa960, 0xa97c}, + {0xa980, 0xa9c0}, + {0xa9cf, 0xa9d9}, + {0xa9e0, 0xa9fe}, + {0xaa00, 0xaa36}, + {0xaa40, 0xaa4d}, + {0xaa50, 0xaa59}, + {0xaa60, 0xaa76}, + {0xaa7a, 0xaac2}, + {0xaadb, 0xaadd}, + {0xaae0, 0xaaef}, + {0xaaf2, 0xaaf6}, + {0xab01, 0xab06}, + {0xab09, 0xab0e}, + {0xab11, 0xab16}, + {0xab20, 0xab26}, + {0xab28, 0xab2e}, + {0xab30, 0xab5a}, + {0xab5c, 0xab69}, + {0xab70, 0xabea}, + {0xabec, 0xabed}, + {0xabf0, 0xabf9}, + {0xac00, 0xd7a3}, + {0xd7b0, 0xd7c6}, + {0xd7cb, 0xd7fb}, + {0xf900, 0xfa6d}, + {0xfa70, 0xfad9}, + {0xfb00, 0xfb06}, + {0xfb13, 0xfb17}, + {0xfb1d, 0xfb28}, + {0xfb2a, 0xfb36}, + {0xfb38, 0xfb3c}, + {0xfb3e, 0xfb3e}, + {0xfb40, 0xfb41}, + {0xfb43, 0xfb44}, + {0xfb46, 0xfbb1}, + {0xfbd3, 0xfc5d}, + {0xfc64, 0xfd3d}, + {0xfd50, 0xfd8f}, + {0xfd92, 0xfdc7}, + {0xfdf0, 0xfdf9}, + {0xfe00, 0xfe0f}, + {0xfe20, 0xfe2f}, + {0xfe33, 0xfe34}, + {0xfe4d, 0xfe4f}, + {0xfe71, 0xfe71}, + {0xfe73, 0xfe73}, + {0xfe77, 0xfe77}, + {0xfe79, 0xfe79}, + {0xfe7b, 0xfe7b}, + {0xfe7d, 0xfe7d}, + {0xfe7f, 0xfefc}, + {0xff10, 0xff19}, + {0xff21, 0xff3a}, + {0xff3f, 0xff3f}, + {0xff41, 0xff5a}, + {0xff65, 0xffbe}, + {0xffc2, 0xffc7}, + {0xffca, 0xffcf}, + {0xffd2, 0xffd7}, + {0xffda, 0xffdc}, + {0x10000, 0x1000b}, + {0x1000d, 0x10026}, + {0x10028, 0x1003a}, + {0x1003c, 0x1003d}, + {0x1003f, 0x1004d}, + {0x10050, 0x1005d}, + {0x10080, 0x100fa}, + {0x10140, 0x10174}, + {0x101fd, 0x101fd}, + {0x10280, 0x1029c}, + {0x102a0, 0x102d0}, + {0x102e0, 0x102e0}, + {0x10300, 0x1031f}, + {0x1032d, 0x1034a}, + {0x10350, 0x1037a}, + {0x10380, 0x1039d}, + {0x103a0, 0x103c3}, + {0x103c8, 0x103cf}, + {0x103d1, 0x103d5}, + {0x10400, 0x1049d}, + {0x104a0, 0x104a9}, + {0x104b0, 0x104d3}, + {0x104d8, 0x104fb}, + {0x10500, 0x10527}, + {0x10530, 0x10563}, + {0x10570, 0x1057a}, + {0x1057c, 0x1058a}, + {0x1058c, 0x10592}, + {0x10594, 0x10595}, + {0x10597, 0x105a1}, + {0x105a3, 0x105b1}, + {0x105b3, 0x105b9}, + {0x105bb, 0x105bc}, + {0x10600, 0x10736}, + {0x10740, 0x10755}, + {0x10760, 0x10767}, + {0x10780, 0x10785}, + {0x10787, 0x107b0}, + {0x107b2, 0x107ba}, + {0x10800, 0x10805}, + {0x10808, 0x10808}, + {0x1080a, 0x10835}, + {0x10837, 0x10838}, + {0x1083c, 0x1083c}, + {0x1083f, 0x10855}, + {0x10860, 0x10876}, + {0x10880, 0x1089e}, + {0x108e0, 0x108f2}, + {0x108f4, 0x108f5}, + {0x10900, 0x10915}, + {0x10920, 0x10939}, + {0x10980, 0x109b7}, + {0x109be, 0x109bf}, + {0x10a00, 0x10a03}, + {0x10a05, 0x10a06}, + {0x10a0c, 0x10a13}, + {0x10a15, 0x10a17}, + {0x10a19, 0x10a35}, + {0x10a38, 0x10a3a}, + {0x10a3f, 0x10a3f}, + {0x10a60, 0x10a7c}, + {0x10a80, 0x10a9c}, + {0x10ac0, 0x10ac7}, + {0x10ac9, 0x10ae6}, + {0x10b00, 0x10b35}, + {0x10b40, 0x10b55}, + {0x10b60, 0x10b72}, + {0x10b80, 0x10b91}, + {0x10c00, 0x10c48}, + {0x10c80, 0x10cb2}, + {0x10cc0, 0x10cf2}, + {0x10d00, 0x10d27}, + {0x10d30, 0x10d39}, + {0x10e80, 0x10ea9}, + {0x10eab, 0x10eac}, + {0x10eb0, 0x10eb1}, + {0x10efd, 0x10f1c}, + {0x10f27, 0x10f27}, + {0x10f30, 0x10f50}, + {0x10f70, 0x10f85}, + {0x10fb0, 0x10fc4}, + {0x10fe0, 0x10ff6}, + {0x11000, 0x11046}, + {0x11066, 0x11075}, + {0x1107f, 0x110ba}, + {0x110c2, 0x110c2}, + {0x110d0, 0x110e8}, + {0x110f0, 0x110f9}, + {0x11100, 0x11134}, + {0x11136, 0x1113f}, + {0x11144, 0x11147}, + {0x11150, 0x11173}, + {0x11176, 0x11176}, + {0x11180, 0x111c4}, + {0x111c9, 0x111cc}, + {0x111ce, 0x111da}, + {0x111dc, 0x111dc}, + {0x11200, 0x11211}, + {0x11213, 0x11237}, + {0x1123e, 0x11241}, + {0x11280, 0x11286}, + {0x11288, 0x11288}, + {0x1128a, 0x1128d}, + {0x1128f, 0x1129d}, + {0x1129f, 0x112a8}, + {0x112b0, 0x112ea}, + {0x112f0, 0x112f9}, + {0x11300, 0x11303}, + {0x11305, 0x1130c}, + {0x1130f, 0x11310}, + {0x11313, 0x11328}, + {0x1132a, 0x11330}, + {0x11332, 0x11333}, + {0x11335, 0x11339}, + {0x1133b, 0x11344}, + {0x11347, 0x11348}, + {0x1134b, 0x1134d}, + {0x11350, 0x11350}, + {0x11357, 0x11357}, + {0x1135d, 0x11363}, + {0x11366, 0x1136c}, + {0x11370, 0x11374}, + {0x11400, 0x1144a}, + {0x11450, 0x11459}, + {0x1145e, 0x11461}, + {0x11480, 0x114c5}, + {0x114c7, 0x114c7}, + {0x114d0, 0x114d9}, + {0x11580, 0x115b5}, + {0x115b8, 0x115c0}, + {0x115d8, 0x115dd}, + {0x11600, 0x11640}, + {0x11644, 0x11644}, + {0x11650, 0x11659}, + {0x11680, 0x116b8}, + {0x116c0, 0x116c9}, + {0x11700, 0x1171a}, + {0x1171d, 0x1172b}, + {0x11730, 0x11739}, + {0x11740, 0x11746}, + {0x11800, 0x1183a}, + {0x118a0, 0x118e9}, + {0x118ff, 0x11906}, + {0x11909, 0x11909}, + {0x1190c, 0x11913}, + {0x11915, 0x11916}, + {0x11918, 0x11935}, + {0x11937, 0x11938}, + {0x1193b, 0x11943}, + {0x11950, 0x11959}, + {0x119a0, 0x119a7}, + {0x119aa, 0x119d7}, + {0x119da, 0x119e1}, + {0x119e3, 0x119e4}, + {0x11a00, 0x11a3e}, + {0x11a47, 0x11a47}, + {0x11a50, 0x11a99}, + {0x11a9d, 0x11a9d}, + {0x11ab0, 0x11af8}, + {0x11c00, 0x11c08}, + {0x11c0a, 0x11c36}, + {0x11c38, 0x11c40}, + {0x11c50, 0x11c59}, + {0x11c72, 0x11c8f}, + {0x11c92, 0x11ca7}, + {0x11ca9, 0x11cb6}, + {0x11d00, 0x11d06}, + {0x11d08, 0x11d09}, + {0x11d0b, 0x11d36}, + {0x11d3a, 0x11d3a}, + {0x11d3c, 0x11d3d}, + {0x11d3f, 0x11d47}, + {0x11d50, 0x11d59}, + {0x11d60, 0x11d65}, + {0x11d67, 0x11d68}, + {0x11d6a, 0x11d8e}, + {0x11d90, 0x11d91}, + {0x11d93, 0x11d98}, + {0x11da0, 0x11da9}, + {0x11ee0, 0x11ef6}, + {0x11f00, 0x11f10}, + {0x11f12, 0x11f3a}, + {0x11f3e, 0x11f42}, + {0x11f50, 0x11f59}, + {0x11fb0, 0x11fb0}, + {0x12000, 0x12399}, + {0x12400, 0x1246e}, + {0x12480, 0x12543}, + {0x12f90, 0x12ff0}, + {0x13000, 0x1342f}, + {0x13440, 0x13455}, + {0x14400, 0x14646}, + {0x16800, 0x16a38}, + {0x16a40, 0x16a5e}, + {0x16a60, 0x16a69}, + {0x16a70, 0x16abe}, + {0x16ac0, 0x16ac9}, + {0x16ad0, 0x16aed}, + {0x16af0, 0x16af4}, + {0x16b00, 0x16b36}, + {0x16b40, 0x16b43}, + {0x16b50, 0x16b59}, + {0x16b63, 0x16b77}, + {0x16b7d, 0x16b8f}, + {0x16e40, 0x16e7f}, + {0x16f00, 0x16f4a}, + {0x16f4f, 0x16f87}, + {0x16f8f, 0x16f9f}, + {0x16fe0, 0x16fe1}, + {0x16fe3, 0x16fe4}, + {0x16ff0, 0x16ff1}, + {0x17000, 0x187f7}, + {0x18800, 0x18cd5}, + {0x18d00, 0x18d08}, + {0x1aff0, 0x1aff3}, + {0x1aff5, 0x1affb}, + {0x1affd, 0x1affe}, + {0x1b000, 0x1b122}, + {0x1b132, 0x1b132}, + {0x1b150, 0x1b152}, + {0x1b155, 0x1b155}, + {0x1b164, 0x1b167}, + {0x1b170, 0x1b2fb}, + {0x1bc00, 0x1bc6a}, + {0x1bc70, 0x1bc7c}, + {0x1bc80, 0x1bc88}, + {0x1bc90, 0x1bc99}, + {0x1bc9d, 0x1bc9e}, + {0x1cf00, 0x1cf2d}, + {0x1cf30, 0x1cf46}, + {0x1d165, 0x1d169}, + {0x1d16d, 0x1d172}, + {0x1d17b, 0x1d182}, + {0x1d185, 0x1d18b}, + {0x1d1aa, 0x1d1ad}, + {0x1d242, 0x1d244}, + {0x1d400, 0x1d454}, + {0x1d456, 0x1d49c}, + {0x1d49e, 0x1d49f}, + {0x1d4a2, 0x1d4a2}, + {0x1d4a5, 0x1d4a6}, + {0x1d4a9, 0x1d4ac}, + {0x1d4ae, 0x1d4b9}, + {0x1d4bb, 0x1d4bb}, + {0x1d4bd, 0x1d4c3}, + {0x1d4c5, 0x1d505}, + {0x1d507, 0x1d50a}, + {0x1d50d, 0x1d514}, + {0x1d516, 0x1d51c}, + {0x1d51e, 0x1d539}, + {0x1d53b, 0x1d53e}, + {0x1d540, 0x1d544}, + {0x1d546, 0x1d546}, + {0x1d54a, 0x1d550}, + {0x1d552, 0x1d6a5}, + {0x1d6a8, 0x1d6c0}, + {0x1d6c2, 0x1d6da}, + {0x1d6dc, 0x1d6fa}, + {0x1d6fc, 0x1d714}, + {0x1d716, 0x1d734}, + {0x1d736, 0x1d74e}, + {0x1d750, 0x1d76e}, + {0x1d770, 0x1d788}, + {0x1d78a, 0x1d7a8}, + {0x1d7aa, 0x1d7c2}, + {0x1d7c4, 0x1d7cb}, + {0x1d7ce, 0x1d7ff}, + {0x1da00, 0x1da36}, + {0x1da3b, 0x1da6c}, + {0x1da75, 0x1da75}, + {0x1da84, 0x1da84}, + {0x1da9b, 0x1da9f}, + {0x1daa1, 0x1daaf}, + {0x1df00, 0x1df1e}, + {0x1df25, 0x1df2a}, + {0x1e000, 0x1e006}, + {0x1e008, 0x1e018}, + {0x1e01b, 0x1e021}, + {0x1e023, 0x1e024}, + {0x1e026, 0x1e02a}, + {0x1e030, 0x1e06d}, + {0x1e08f, 0x1e08f}, + {0x1e100, 0x1e12c}, + {0x1e130, 0x1e13d}, + {0x1e140, 0x1e149}, + {0x1e14e, 0x1e14e}, + {0x1e290, 0x1e2ae}, + {0x1e2c0, 0x1e2f9}, + {0x1e4d0, 0x1e4f9}, + {0x1e7e0, 0x1e7e6}, + {0x1e7e8, 0x1e7eb}, + {0x1e7ed, 0x1e7ee}, + {0x1e7f0, 0x1e7fe}, + {0x1e800, 0x1e8c4}, + {0x1e8d0, 0x1e8d6}, + {0x1e900, 0x1e94b}, + {0x1e950, 0x1e959}, + {0x1ee00, 0x1ee03}, + {0x1ee05, 0x1ee1f}, + {0x1ee21, 0x1ee22}, + {0x1ee24, 0x1ee24}, + {0x1ee27, 0x1ee27}, + {0x1ee29, 0x1ee32}, + {0x1ee34, 0x1ee37}, + {0x1ee39, 0x1ee39}, + {0x1ee3b, 0x1ee3b}, + {0x1ee42, 0x1ee42}, + {0x1ee47, 0x1ee47}, + {0x1ee49, 0x1ee49}, + {0x1ee4b, 0x1ee4b}, + {0x1ee4d, 0x1ee4f}, + {0x1ee51, 0x1ee52}, + {0x1ee54, 0x1ee54}, + {0x1ee57, 0x1ee57}, + {0x1ee59, 0x1ee59}, + {0x1ee5b, 0x1ee5b}, + {0x1ee5d, 0x1ee5d}, + {0x1ee5f, 0x1ee5f}, + {0x1ee61, 0x1ee62}, + {0x1ee64, 0x1ee64}, + {0x1ee67, 0x1ee6a}, + {0x1ee6c, 0x1ee72}, + {0x1ee74, 0x1ee77}, + {0x1ee79, 0x1ee7c}, + {0x1ee7e, 0x1ee7e}, + {0x1ee80, 0x1ee89}, + {0x1ee8b, 0x1ee9b}, + {0x1eea1, 0x1eea3}, + {0x1eea5, 0x1eea9}, + {0x1eeab, 0x1eebb}, + {0x1fbf0, 0x1fbf9}, + {0x20000, 0x2a6df}, + {0x2a700, 0x2b739}, + {0x2b740, 0x2b81d}, + {0x2b820, 0x2cea1}, + {0x2ceb0, 0x2ebe0}, + {0x2ebf0, 0x2ee5d}, + {0x2f800, 0x2fa1d}, + {0x30000, 0x3134a}, + {0x31350, 0x323af}, + {0xe0100, 0xe01ef} +}; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif diff --git a/contrib/tinyusdz/tinyusdz_repo/src/unicode-xid.hh b/contrib/tinyusdz/tinyusdz_repo/src/unicode-xid.hh new file mode 100644 index 000000000..82795a4d2 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/unicode-xid.hh @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +// Copyright 2024 - Present, Light Transport Entertainment Inc. +// +// UTF-8 Unicode identifier XID_Start and XID_Continue validation utility. +// +// Based on UAX31 Default Identifier and Unicode 5.1.0 +#pragma once + +#include +#include +#include +#include +#include + +namespace unicode_xid { + +constexpr uint32_t kMaxCodepoint = 0x10FFFF; + +namespace detail { + +// Assume table is sorted by the first key(range_begin) +#include "unicode-xid-table.inc" + +} + +inline bool is_xid_start(uint32_t codepoint) { + if (codepoint > kMaxCodepoint) { + return false; + } + + // first a range(range_begin <= codepoint <= range_end) by comparing the second key(range end). + auto it = std::lower_bound(detail::kXID_StartTable.begin(), detail::kXID_StartTable.end(), int(codepoint), [](const std::pair &a, const int b) { + return a.second < b; + }); + + if (it != detail::kXID_StartTable.end()) { + if ((int(codepoint) >= it->first) && (int(codepoint) <= it->second)) { // range end is inclusive. + return true; + } + } + + return false; +} + +inline bool is_xid_continue(uint32_t codepoint) { + if (codepoint > kMaxCodepoint) { + return false; + } + + auto it = std::lower_bound(detail::kXID_ContinueTable.begin(), detail::kXID_ContinueTable.end(), int(codepoint), [](const std::pair &a, const int b) { + return a.second < b; + }); + + if (it != detail::kXID_ContinueTable.end()) { + if ((int(codepoint) >= it->first) && (int(codepoint) <= it->second)) { // range end is inclusive. + return true; + } + } + + return false; +} + +} // namespace unicode_xid + + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usdGeom.cc b/contrib/tinyusdz/tinyusdz_repo/src/usdGeom.cc new file mode 100644 index 000000000..7c2e799d2 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usdGeom.cc @@ -0,0 +1,865 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +// UsdGeom API implementations + + +#include + +#include "pprinter.hh" +#include "value-types.hh" +#include "prim-types.hh" +#include "str-util.hh" +#include "tiny-format.hh" +#include "xform.hh" +#include "usdGeom.hh" +// +//#include "external/simple_match/include/simple_match/simple_match.hpp" +// +#include "common-macros.inc" +#include "math-util.inc" +#include "str-util.hh" +#include "value-pprint.hh" + +#define SET_ERROR_AND_RETURN(msg) \ + if (err) { \ + (*err) = (msg); \ + } \ + return false + +namespace tinyusdz { + +namespace { + +constexpr auto kPrimvars = "primvars:"; +constexpr auto kIndices = ":indices"; + +/// +/// Computes +/// +/// for i in len(indices): +/// for k in elementSize: +/// dest[i*elementSize + k] = values[indices[i]*elementSize + k] +/// +/// `dest` is set to `values` when `indices` is empty +/// +template +nonstd::expected ExpandWithIndices( + const std::vector &values, uint32_t elementSize, const std::vector &indices, + std::vector *dest) { + if (!dest) { + return nonstd::make_unexpected("`dest` is nullptr."); + } + + if (indices.empty()) { + (*dest) = values; + return true; + } + + if (elementSize == 0) { + return false; + } + + if ((values.size() % elementSize) != 0) { + return false; + } + + dest->resize(indices.size() * elementSize); + + std::vector invalidIndices; + + bool valid = true; + for (size_t i = 0; i < indices.size(); i++) { + int32_t idx = indices[i]; + if ((idx >= 0) && ((size_t(idx+1) * size_t(elementSize)) <= values.size())) { + for (size_t k = 0; k < elementSize; k++) { + (*dest)[i*elementSize + k] = values[size_t(idx)*elementSize + k]; + } + } else { + invalidIndices.push_back(i); + valid = false; + } + } + + if (invalidIndices.size()) { + return nonstd::make_unexpected( + "Invalid indices found: " + + value::print_array_snipped(invalidIndices, + /* N to display */ 5)); + } + + return valid; +} + +} // namespace + +bool IsSupportedGeomPrimvarType(uint32_t tyid) { + // + // scalar and 1D + // +#define SUPPORTED_TYPE_FUN(__ty) \ + case value::TypeTraits<__ty>::type_id(): { \ + return true; \ + } \ + case (value::TypeTraits<__ty>::type_id() | value::TYPE_ID_1D_ARRAY_BIT): { \ + return true; \ + } + + switch (tyid) { + APPLY_GEOMPRIVAR_TYPE(SUPPORTED_TYPE_FUN) + default: + return false; + } + +#undef SUPPORTED_TYPE_FUN +} + +bool IsSupportedGeomPrimvarType(const std::string &type_name) { + return IsSupportedGeomPrimvarType(value::GetTypeId(type_name)); +} + +bool GeomPrimvar::has_elementSize() const { + return _elementSize.has_value(); +} + +uint32_t GeomPrimvar::get_elementSize() const { + if (_elementSize.has_value()) { + return _elementSize.value(); + } + return 1; +} + +bool GeomPrimvar::has_interpolation() const { + return _interpolation.has_value(); +} + +Interpolation GeomPrimvar::get_interpolation() const { + if (_interpolation.has_value()) { + return _interpolation.value(); + } + + return Interpolation::Constant; // unauthored +} + +bool GPrim::has_primvar(const std::string &varname) const { + std::string primvar_name = kPrimvars + varname; + return props.count(primvar_name); +} + +bool GPrim::get_primvar(const std::string &varname, GeomPrimvar *out_primvar, + std::string *err) const { + if (!out_primvar) { + SET_ERROR_AND_RETURN("Output GeomPrimvar is nullptr."); + } + + GeomPrimvar primvar; + + std::string primvar_name = kPrimvars + varname; + + const auto it = props.find(primvar_name); + if (it == props.end()) { + return false; + } + + if (it->second.is_attribute()) { + const Attribute &attr = it->second.get_attribute(); + + primvar.set_value(attr); + primvar.set_name(varname); + if (attr.metas().interpolation.has_value()) { + primvar.set_interpolation(attr.metas().interpolation.value()); + } + if (attr.metas().elementSize.has_value()) { + primvar.set_elementSize(attr.metas().elementSize.value()); + } + + } else { + SET_ERROR_AND_RETURN(fmt::format("{} is not Attribute. Maybe Relationship?", primvar_name)); + } + + // has indices? + std::string index_name = primvar_name + kIndices; + const auto indexIt = props.find(index_name); + + if (indexIt != props.end()) { + if (indexIt->second.is_attribute()) { + + if (!(primvar.get_attribute().type_id() & value::TYPE_ID_1D_ARRAY_BIT)) { + SET_ERROR_AND_RETURN( + fmt::format("Indexed GeomPrimVar with scalar PrimVar Attribute is not supported. PrimVar name: {}", primvar_name)); + } + + const Attribute &indexAttr = indexIt->second.get_attribute(); + + if (indexAttr.is_connection()) { + SET_ERROR_AND_RETURN( + "Attribute Connetion is not supported for index Attribute, since we need Stage info to find Prim referred by targetPath. Use Tydra API tydra::GetGeomPrimvar."); + } else if (indexAttr.is_timesamples()) { + const auto &ts = indexAttr.get_var().ts_raw(); + TypedTimeSamples> tss; + if (!tss.from_timesamples(ts)) { + SET_ERROR_AND_RETURN(fmt::format("Index Attribute seems not an timesamples with int[] type: {}", index_name)); + } + + primvar.set_indices(tss); + } else if (indexAttr.is_blocked()) { + // ignore Index attribute. + } else if (indexAttr.is_value()) { + // Check if int[] type. + // TODO: Support uint[]? + std::vector indices; + if (!indexAttr.get_value(&indices)) { + SET_ERROR_AND_RETURN( + fmt::format("Index Attribute is not int[] type. Got {}", + indexAttr.type_name())); + } + + primvar.set_indices(indices); + } else { + SET_ERROR_AND_RETURN("[Internal Error] Invalid Index Attribute."); + } + } else { + // indices are optional, so ok to skip it. + } + } + + (*out_primvar) = primvar; + + return true; +} + + +template +bool GeomPrimvar::flatten_with_indices(const double t, std::vector *dest, const value::TimeSampleInterpolationType tinterp, std::string *err) const { + if (!dest) { + if (err) { + (*err) += "Output value is nullptr."; + } + return false; + } + + if (_attr.is_timesamples() || _attr.is_value()) { + + if (!IsSupportedGeomPrimvarType(_attr.type_id())) { + if (err) { + (*err) += fmt::format("Unsupported type for GeomPrimvar. type = `{}`", + _attr.type_name()); + } + return false; + } + + std::vector value; + if (_attr.get_value>(t, &value, tinterp)) { + + if (_indices.empty()) { + (*dest) = value; + return true; + } + + uint32_t elementSize = _attr.metas().elementSize.value_or(1); + + std::vector indices; + // Get indices at specified time + _indices.get(&indices, t, tinterp); + + std::vector expanded_val; + auto ret = ExpandWithIndices(value, elementSize, indices, &expanded_val); + if (ret) { + (*dest) = expanded_val; + // Currently we ignore ret.value() + return true; + } else { + const std::string &err_msg = ret.error(); + if (err) { + (*err) += fmt::format( + "[Internal Error] Failed to expand for GeomPrimvar type = `{}`", + _attr.type_name()); + if (err_msg.size()) { + (*err) += "\n" + err_msg; + } + } + } + } else { + if (err) { + (*err) += fmt::format( + "`{}[]` type requested, but Attribute is type `{}`", + value::TypeTraits::type_name(), _attr.type_name()); + } + } + } else { + // TODO: Report error? + } + + return false; +} + +template +bool GeomPrimvar::flatten_with_indices(std::vector *dest, std::string *err) const { + return flatten_with_indices(value::TimeCode::Default(), dest, value::TimeSampleInterpolationType::Linear, err); +} + +// instanciation +#define INSTANCIATE_FLATTEN_WITH_INDICES(__ty) \ + template bool GeomPrimvar::flatten_with_indices(std::vector<__ty> *dest, std::string *err) const; \ + template bool GeomPrimvar::flatten_with_indices(const double t, std::vector<__ty> *dest, const value::TimeSampleInterpolationType tinterp, std::string *err) const; + +APPLY_GEOMPRIVAR_TYPE(INSTANCIATE_FLATTEN_WITH_INDICES) + +#undef INSTANCIATE_FLATTEN_WITH_INDICES + +bool GeomPrimvar::flatten_with_indices(const double t, value::Value *dest, const value::TimeSampleInterpolationType tinterp, std::string *err) const { + + if (!dest) { + if (err) { + (*err) += "Output value is nullptr."; + } + return false; + } + + bool processed = false; + + if (_attr.is_value() || _attr.is_timesamples()) { + if (!IsSupportedGeomPrimvarType(_attr.type_id())) { + if (err) { + (*err) += fmt::format("Unsupported type for GeomPrimvar. type = `{}`", + _attr.type_name()); + } + return false; + } + + value::Value val; + + if (!(_attr.type_id() & value::TYPE_ID_1D_ARRAY_BIT)) { + + // evaluate value at specified time and return it for scalar type. + value::Value v; + if (!_attr.get_var().get_interpolated_value(t, tinterp, &v)) { + if (err) { + (*err) += fmt::format("Failed to evaluate Attribute value."); + } + return false; + } + (*dest) = v; + } else { + std::string err_msg; + + uint32_t elementSize = _attr.metas().elementSize.value_or(1); + + std::vector indices; + // Get indices at default time + _indices.get(&indices); + +#define APPLY_FUN(__ty) \ + case value::TypeTraits<__ty>::type_id() | value::TYPE_ID_1D_ARRAY_BIT: { \ + std::vector<__ty> value; \ + std::vector<__ty> expanded_val; \ + if (_attr.get_value(t, &value, tinterp)) { \ + auto ret = ExpandWithIndices(value, elementSize, indices, &expanded_val); \ + if (ret) { \ + processed = ret.value(); \ + if (processed) { \ + val = expanded_val; \ + } \ + } else { \ + err_msg = ret.error(); \ + } \ + } \ + break; \ + } + + switch (_attr.type_id()) { + APPLY_GEOMPRIVAR_TYPE(APPLY_FUN) + default: { + processed = false; + } + } + +#undef APPLY_FUN + + if (processed) { + (*dest) = std::move(val); + } else { + if (err) { + (*err) += fmt::format( + "[Internal Error] Failed to expand for GeomPrimvar type = `{}`", + _attr.type_name()); + if (err_msg.size()) { + (*err) += "\n" + err_msg; + } + } + } + } + } + + return processed; +} + +bool GeomPrimvar::flatten_with_indices(value::Value *dest, std::string *err) const { + return flatten_with_indices(value::TimeCode::Default(), dest, value::TimeSampleInterpolationType::Linear, err); +} + +template +bool GeomPrimvar::get_value(T *dest, std::string *err) const { + static_assert(tinyusdz::value::TypeTraits::type_id() != value::TypeTraits::type_id(), "`token` type is not supported as a GeomPrimvar"); + static_assert(tinyusdz::value::TypeTraits::type_id() != value::TypeTraits>::type_id(), "`token[]` type is not supported as a GeomPrimvar"); + + if (!dest) { + if (err) { + (*err) += "Output value is nullptr."; + } + return false; + } + + if (_attr.is_timesamples()) { + if (err) { + (*err) += "TimeSamples attribute is TODO."; + } + return false; + } + + if (_attr.is_blocked()) { + if (err) { + (*err) += "Attribute is blocked."; + } + return false; + } + + if (_attr.is_value()) { + if (!IsSupportedGeomPrimvarType(_attr.type_id())) { + if (err) { + (*err) += fmt::format("Unsupported type for GeomPrimvar. type = `{}`", + _attr.type_name()); + } + return false; + } + + if (auto pv = _attr.get_value()) { + + // copy + (*dest) = pv.value(); + return true; + + } else { + if (err) { + (*err) += fmt::format("Attribute value type mismatch. Requested type `{}` but Attribute has type `{}`", value::TypeTraits::type_id(), _attr.type_name()); + } + return false; + } + } + + return false; +} + +template +bool GeomPrimvar::get_value(double timecode, T *dest, value::TimeSampleInterpolationType interp, std::string *err) const { + static_assert(tinyusdz::value::TypeTraits::type_id() != value::TypeTraits::type_id(), "`token` type is not supported as a GeomPrimvar"); + static_assert(tinyusdz::value::TypeTraits::type_id() != value::TypeTraits>::type_id(), "`token[]` type is not supported as a GeomPrimvar"); + + if (!dest) { + if (err) { + (*err) += "Output value is nullptr."; + } + return false; + } + + if (_attr.is_blocked()) { + if (err) { + (*err) += "Attribute is blocked."; + } + return false; + } + + if (!IsSupportedGeomPrimvarType(_attr.type_id())) { + if (err) { + (*err) += fmt::format("Unsupported type for GeomPrimvar. type = `{}`", + _attr.type_name()); + } + return false; + } + + if (_attr.is_timesamples()) { + T value; + + if (!_attr.get_value(timecode, &value, interp)) { + if (err) { + (*err) += fmt::format("Get Attribute value at time {} failed. Maybe type mismatch?. Requested type `{}` but Attribute has type `{}`", timecode, value::TypeTraits::type_id(), _attr.type_name()); + } + return false; + } + + // copy + (*dest) = value; + return true; + + } else if (_attr.is_value()) { + + if (auto pv = _attr.get_value()) { + + // copy + (*dest) = pv.value(); + return true; + + } else { + if (err) { + (*err) += fmt::format("Attribute value type mismatch. Requested type `{}` but Attribute has type `{}`", value::TypeTraits::type_id(), _attr.type_name()); + } + return false; + } + } + + return false; +} + +// instanciation +#define INSTANCIATE_GET_VALUE(__ty) \ + template bool GeomPrimvar::get_value(__ty *dest, std::string *err) const; \ + template bool GeomPrimvar::get_value(double, __ty *dest, value::TimeSampleInterpolationType, std::string *err) const; \ + template bool GeomPrimvar::get_value(std::vector<__ty> *dest, std::string *err) const; \ + template bool GeomPrimvar::get_value(double, std::vector<__ty> *dest, value::TimeSampleInterpolationType, std::string *err) const; + +APPLY_GEOMPRIVAR_TYPE(INSTANCIATE_GET_VALUE) + +#undef INSTANCIATE_GET_VALUE + +std::vector GPrim::get_primvars() const { + std::vector gpvars; + + for (const auto &prop : props) { + if (startsWith(prop.first, kPrimvars)) { + // skip `:indices`. Attribute with `:indices` suffix is handled in + // `get_primvar` + if (props.count(prop.first + kIndices)) { + continue; + } + + GeomPrimvar gprimvar; + if (get_primvar(removePrefix(prop.first, kPrimvars), &gprimvar)) { + gpvars.emplace_back(std::move(gprimvar)); + } + } + } + + return gpvars; +} + +bool GPrim::set_primvar(const GeomPrimvar &primvar, + std::string *err) { + if (primvar.name().empty()) { + if (err) { + (*err) += "GeomPrimvar.name is empty."; + } + return false; + } + + if (startsWith(primvar.name(), "primvars:")) { + if (err) { + (*err) += "GeomPrimvar.name must not start with `primvars:` namespace. name = " + primvar.name(); + } + return false; + } + + std::string primvar_name = kPrimvars + primvar.name(); + + // Overwrite existing primvar prop. + // TODO: Report warn when primvar name already exists. + + Attribute attr = primvar.get_attribute(); + + if (primvar.has_interpolation()) { + attr.metas().interpolation = primvar.get_interpolation(); + } + + if (primvar.has_elementSize()) { + attr.metas().elementSize = primvar.get_elementSize(); + } + + props[primvar_name] = attr; + + if (primvar.has_indices()) { + + std::string index_name = primvar_name + kIndices; + + Attribute indices; + + for (const auto &sample : primvar.get_indices().get_samples()) { + indices.set_timesample(sample.value, sample.t); + } + + props[index_name] = indices; + } + + return true; +} + +bool GPrim::get_displayColor(value::color3f *dst, double t, const value::TimeSampleInterpolationType tinterp) +{ + // TODO: timeSamples + (void)t; + (void)tinterp; + + GeomPrimvar primvar; + std::string err; + if (!get_primvar("displayColor", &primvar, &err)) { + // TODO: report err + return false; + } + + return primvar.get_value(dst); +} + +bool GPrim::get_displayOpacity(float *dst, double t, const value::TimeSampleInterpolationType tinterp) +{ + // TODO: timeSamples + (void)t; + (void)tinterp; + + GeomPrimvar primvar; + std::string err; + if (!get_primvar("displayOpacity", &primvar, &err)) { + // TODO: report err + return false; + } + + return primvar.get_value(dst); +} + +const std::vector GeomMesh::get_points( + double time, value::TimeSampleInterpolationType interp) const { + std::vector dst; + + if (!points.authored() || points.is_blocked()) { + return dst; + } + + if (points.is_connection()) { + // TODO: connection + return dst; + } + + if (auto pv = points.get_value()) { + std::vector val; + if (pv.value().get(time, &val, interp)) { + dst = std::move(val); + } + } + + return dst; +} + +const std::vector GeomMesh::get_normals( + double time, value::TimeSampleInterpolationType interp) const { + std::vector dst; + + std::string err; + if (has_primvar("normals")) { + GeomPrimvar primvar; + if (!get_primvar("normals", &primvar, &err)) { + return dst; + } + + primvar.flatten_with_indices(time, &dst, interp); + return dst; + } else if (normals.authored()) { + if (normals.is_connection()) { + // Not supported + return dst; + } else if (normals.is_blocked()) { + return dst; + } + + std::vector indices; + if (props.count("normals:indices")) { + Attribute indexAttr = props.at("normals:indices").get_attribute(); + + if (indexAttr.is_connection()) { + // not supported. + return dst; + } + + if (!indexAttr.get_value(time, &indices, interp)) { + // err + return dst; + } + + } + + std::vector value; + if (!normals.get_value().value().get(time, &value, interp)) { + return dst; + } + + if (indices.size()) { + uint32_t elementSize = normals.metas().elementSize.value_or(1); + + std::vector expanded_normals; + auto ret = ExpandWithIndices(value, elementSize, indices, &expanded_normals); + + if (!ret) { + return dst; + } + + dst = expanded_normals; + } else { + dst = value; + } + } + + return dst; +} + +Interpolation GeomMesh::get_normalsInterpolation() const { + if (props.count("primvars:normals")) { + const auto &prop = props.at("primvars:normals"); + if (prop.get_attribute().type_name() == "normal3f[]") { + if (prop.get_attribute().metas().interpolation) { + return prop.get_attribute().metas().interpolation.value(); + } + } + } else if (normals.metas().interpolation) { + return normals.metas().interpolation.value(); + } + + return Interpolation::Vertex; // default 'vertex' +} + +const std::vector GeomMesh::get_faceVertexCounts() const { + std::vector dst; + + if (!faceVertexCounts.authored() || faceVertexCounts.is_blocked()) { + return dst; + } + + if (faceVertexCounts.is_connection()) { + // TODO: connection + return dst; + } + + if (auto pv = faceVertexCounts.get_value()) { + std::vector val; + // TOOD: timesamples + if (pv.value().get_scalar(&val)) { + dst = std::move(val); + } + } + return dst; +} + +const std::vector GeomMesh::get_faceVertexIndices() const { + std::vector dst; + + if (!faceVertexIndices.authored() || faceVertexIndices.is_blocked()) { + return dst; + } + + if (faceVertexIndices.is_connection()) { + // TODO: connection + return dst; + } + + if (auto pv = faceVertexIndices.get_value()) { + std::vector val; + // TOOD: timesamples + if (pv.value().get_scalar(&val)) { + dst = std::move(val); + } + } + return dst; +} + +// static +bool GeomSubset::ValidateSubsets( + const std::vector &subsets, + const size_t elementCount, + const FamilyType &familyType, std::string *err) { + + if (subsets.empty()) { + return true; + } + + // All subsets must have the same elementType. + GeomSubset::ElementType elementType = subsets[0]->elementType.get_value(); + for (const auto psubset : subsets) { + if (psubset->elementType.get_value() != elementType) { + if (err) { + (*err) = fmt::format("GeomSubset {}'s elementType must be `{}`, but got `{}`.\n", + psubset->name, to_string(elementType), to_string(psubset->elementType.get_value())); + } + + return false; + } + } + + std::set indicesInFamily; + + bool valid = true; + std::stringstream ss; + + // TODO: TimeSampled indices + for (const auto psubset : subsets) { + Animatable> indices; + if (!psubset->indices.get_value(&indices)) { + ss << fmt::format("GeomSubset {}'s indices is not value Attribute. Connection or ValueBlock?\n", + psubset->name); + + valid = false; + } + + if (indices.is_blocked()) { + ss << fmt::format("GeomSubset {}'s indices is Value Blocked.\n", psubset->name); + valid = false; + } + + if (indices.is_timesamples()) { + ss << fmt::format("ValidateSubsets: TimeSampled GeomSubset.indices is not yet supported.\n"); + valid = false; + } + + std::vector subsetIndices; + if (!indices.get_scalar(&subsetIndices)) { + ss << fmt::format("ValidateSubsets: Internal error. Failed to get GeomSubset.indices.\n"); + valid = false; + } + + for (const int32_t index : subsetIndices) { + if (!indicesInFamily.insert(index).second && (familyType != FamilyType::Unrestricted)) { + ss << fmt::format("Found overlapping index {} in GeomSubset `{}`\n", index, psubset->name); + valid = false; + } + } + + // Make sure every index appears exactly once if it's a partition. + if ((familyType == FamilyType::Partition) && (indicesInFamily.size() != elementCount)) { + ss << fmt::format("ValidateSubsets: The number of unique indices {} must be equal to input elementCount {}\n", indicesInFamily.size(), elementCount); + valid = false; + } + + // Ensure that the indices are in the range [0, faceCount) + size_t maxIndex = static_cast(*indicesInFamily.rbegin()); + int minIndex = *indicesInFamily.begin(); + + if (maxIndex >= elementCount) { + ss << fmt::format("ValidateSubsets: All indices must be in range [0, elementSize {}), but one or more indices are greater than elementSize. Maximum = {}\n", elementCount, maxIndex); + + valid = false; + } + + if (minIndex < 0) { + ss << fmt::format("ValidateSubsets: Found one or more indices that are less than 0. Minumum = {}\n", minIndex); + + valid = false; + } + + } + + if (!valid) { + if (err) { + (*err) += ss.str(); + } + } + + return true; + +} + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usdGeom.hh b/contrib/tinyusdz/tinyusdz_repo/src/usdGeom.hh new file mode 100644 index 000000000..fc79848b9 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usdGeom.hh @@ -0,0 +1,1176 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +// UsdGeom +// +// TODO +// +// - [ ] Replace nonstd::optional member to RelationshipProperty or TypedAttribute*** +// +#pragma once + +#include "prim-types.hh" +#include "value-types.hh" +#include "xform.hh" +#include "usdShade.hh" + +namespace tinyusdz { + +// From schema definition. +constexpr auto kGPrim = "GPrim"; +constexpr auto kGeomCube = "Cube"; +constexpr auto kGeomXform = "Xform"; +constexpr auto kGeomMesh = "Mesh"; +constexpr auto kGeomSubset = "GeomSubset"; +constexpr auto kGeomBasisCurves = "BasisCurves"; +constexpr auto kGeomNurbsCurves = "NurbsCurves"; +constexpr auto kGeomCylinder = "Cylinder"; +constexpr auto kGeomCapsule = "Capsule"; +constexpr auto kGeomPoints = "Points"; +constexpr auto kGeomCone = "Cone"; +constexpr auto kGeomSphere = "Sphere"; +constexpr auto kGeomCamera = "Camera"; +constexpr auto kPointInstancer = "PointInstancer"; + +constexpr auto kMaterialBinding = "material:binding"; +constexpr auto kMaterialBindingCollection = "material:binding:collection"; +constexpr auto kMaterialBindingPreview = "material:binding:preview"; +constexpr auto kMaterialBindingFull = "material:binding:full"; + +struct GPrim; + +bool IsSupportedGeomPrimvarType(uint32_t tyid); +bool IsSupportedGeomPrimvarType(const std::string &type_name); + +// +// GeomPrimvar is a wrapper class for Attribute and indices(for Indexed Primvar) +// - Attribute with `primvars` prefix. e.g. "primvars: +// - Optional: indices. +// +// GeomPrimvar is only constructable from GPrim. +// This class COPIES variable from GPrim for `get` operation. +// +// Currently read-only operation is well provided. writing feature is not well tested(`set_value` may have issue) +// (If you struggled to ue GeomPrimvar, please operate on `GPrim::props` directly) +// +// Limitation: +// TimeSamples are not supported for indices. +// Also, TimeSamples are not supported both when constructing GeomPrimvar with Typed Attribute value and retrieving Attribute value. +// +// +class GeomPrimvar { + + friend GPrim; + + public: + GeomPrimvar() : _has_value(false) { + } + + GeomPrimvar(const Attribute &attr) : _attr(attr) { + _has_value = true; + } + + GeomPrimvar(const Attribute &attr, const std::vector &indices) : _attr(attr) + { + _indices.add_sample(value::TimeCode::Default(), indices); + _has_value = true; + } + + GeomPrimvar(const Attribute &attr, const TypedTimeSamples> &indices) : _attr(attr) + { + _indices = indices; + _has_value = true; + } + + GeomPrimvar(const Attribute &attr, TypedTimeSamples> &&indices) : _attr(attr) + { + _indices = std::move(indices); + _has_value = true; + } + + GeomPrimvar(const GeomPrimvar &rhs) { + _name = rhs._name; + _attr = rhs._attr; + _indices = rhs._indices; + _has_value = rhs._has_value; + if (rhs._elementSize) { + _elementSize = rhs._elementSize; + } + + if (rhs._interpolation) { + _interpolation = rhs._interpolation; + } + } + + GeomPrimvar &operator=(const GeomPrimvar &rhs) { + _name = rhs._name; + _attr = rhs._attr; + _indices = rhs._indices; + _has_value = rhs._has_value; + if (rhs._elementSize) { + _elementSize = rhs._elementSize; + } + + if (rhs._interpolation) { + _interpolation = rhs._interpolation; + } + + return *this; + } + + /// + /// For Indexed Primvar(array value + indices) + /// + /// equivalent to ComputeFlattened in pxrUSD. + /// + /// ``` + /// for i in len(indices): + /// dest[i] = values[indices[i]] + /// ``` + /// + /// Use Default time and Linear interpolation when `indices` and/or primvar is timesamples. + /// + /// If Primvar does not have indices, return attribute value as is(same with `get_value`). + /// For now, we only support Attribute with 1D array type. + /// + /// Return false when operation failed or if the attribute type is not supported for Indexed Primvar. + /// + /// + template + bool flatten_with_indices(std::vector *dst, std::string *err = nullptr) const; + + /// + /// Specify time and interpolation type. + /// + template + bool flatten_with_indices(double t, std::vector *dst, value::TimeSampleInterpolationType tinerp = value::TimeSampleInterpolationType::Linear, std::string *err = nullptr) const; + + + // Generic Value version. + // TODO: return Attribute? + bool flatten_with_indices(value::Value *dst, std::string *err = nullptr) const; + bool flatten_with_indices(double t, value::Value *dst, value::TimeSampleInterpolationType tinterp = value::TimeSampleInterpolationType::Linear, std::string *err = nullptr) const; + + bool has_elementSize() const; + uint32_t get_elementSize() const; + + bool has_interpolation() const; + Interpolation get_interpolation() const; + + void set_elementSize(uint32_t n) { + _elementSize = n; + } + + void set_interpolation(const Interpolation interp) { + _interpolation = interp; + } + + const TypedTimeSamples> &get_indices() const { + return _indices; + } + + bool has_indices() const { return !_indices.empty(); } + + uint32_t type_id() const { return _attr.type_id(); } + std::string type_name() const { return _attr.type_name(); } + + // Name of Primvar. "primvars:" prefix(namespace) is omitted. + const std::string name() const { return _name; } + + /// + /// Attribute has value?(Not empty) + /// + bool has_value() const { + return _has_value; + } + + /// + /// Get type name of primvar. + /// + std::string get_type_name() const { + if (!_has_value) { + return "null"; + } + + return _attr.type_name(); + } + + /// + /// Get type id of primvar. + /// + uint32_t get_type_id() const { + if (!_has_value) { + return value::TYPE_ID_NULL; + } + return _attr.type_id(); + } + + /// + /// Get Attribute value. + /// + template + bool get_value(T *dst, std::string *err = nullptr) const; + + bool get_value(value::Value *dst, std::string *err = nullptr) const; + + + /// + /// Get Attribute value at specified time. + /// + template + bool get_value(double timecode, T *dst, const value::TimeSampleInterpolationType interp = value::TimeSampleInterpolationType::Linear, std::string *err = nullptr) const; + + bool get_value(double timecode, value::Value *dst, const value::TimeSampleInterpolationType interp = value::TimeSampleInterpolationType::Linear, std::string *err = nullptr) const; + + /// + /// Set Attribute value. + /// + template + void set_value(const T &val) { + _attr.set_value(val); + _has_value = true; + } + + void set_value(const Attribute &attr) { + _attr = attr; + _has_value = true; + } + + void set_value(const Attribute &&attr) { + _attr = std::move(attr); + _has_value = true; + } + + void set_name(const std::string &name) { _name = name; } + + void set_indices(const std::vector &indices) { + _indices.add_sample(value::TimeCode::Default(), indices); + } + + void set_indices(const std::vector &&indices) { + _indices.add_sample(value::TimeCode::Default(), std::move(indices)); + } + + void set_indices(const TypedTimeSamples> &indices) { + _indices = indices; + } + + const Attribute &get_attribute() const { + return _attr; + } + + private: + + std::string _name; + bool _has_value{false}; + Attribute _attr; + //std::vector _indices; // TODO: uint support? + TypedTimeSamples> _indices; + + // Store Attribute meta separately. + nonstd::optional _elementSize; + nonstd::optional _interpolation; + +#if 0 // TODO + bool get_value(const value::Value *value, + const double t = value::TimeCode::Default(), + const value::TimeSampleInterpolationType tinterp = + value::TimeSampleInterpolationType::Held); +#endif + +}; + +// Geometric Prim. Encapsulates Imagable + Boundable in pxrUSD schema. +// /pxr/usd/usdGeom/schema.udsa +// +// TODO: inherit UsdShagePrim? + +struct GPrim : Xformable, MaterialBinding, Collection { + std::string name; + Specifier spec{Specifier::Def}; + + int64_t parent_id{-1}; // Index to parent node + + std::string prim_type; // Primitive type(if specified by `def`) + + void set_name(const std::string &name_) { + name = name_; + } + + const std::string &get_name() const { + return name; + } + + Specifier &specifier() { return spec; } + const Specifier &specifier() const { return spec; } + + // Gprim + + TypedAttribute> + extent; // bounding extent. When authorized, the extent is the bounding + // box of whole its children. + + TypedAttributeWithFallback doubleSided{ + false}; // "uniform bool doubleSided" + + TypedAttributeWithFallback orientation{ + Orientation::RightHanded}; // "uniform token orientation" + TypedAttributeWithFallback> visibility{ + Visibility::Inherited}; // "token visibility" + TypedAttributeWithFallback purpose{ + Purpose::Default}; // "uniform token purpose" + + // Handy API to get `primvars:displayColor` and `primvars:displayOpacity` + bool get_displayColor(value::color3f *col, const double t = value::TimeCode::Default(), const value::TimeSampleInterpolationType tinterp = value::TimeSampleInterpolationType::Linear); + + bool get_displayOpacity(float *opacity, const double t = value::TimeCode::Default(), const value::TimeSampleInterpolationType tinterp = value::TimeSampleInterpolationType::Linear); + + RelationshipProperty proxyPrim; + +#if 0 + // Some frequently used materialBindings + nonstd::optional materialBinding; // material:binding + nonstd::optional materialBindingCollection; // material:binding:collection TODO: deprecate?(seems `material:binding:collection` without leaf NAME seems ignored in pxrUSD. + nonstd::optional materialBindingPreview; // material:binding:preview + nonstd::optional materialBindingFull; // material:binding:full +#endif + + std::map props; + + std::pair> references; + std::pair> payload; + std::map variantSet; + + // For GeomPrimvar. + + /// + /// Get Attribute(+ indices Attribute for Indexed Primvar) with "primvars:" suffix(namespace) in `props` + /// + /// NOTE: This API does not support Connection Atttribute(e.g. `int[] primvars:uvs:indices = `) + /// If you want to get Primvar with possible Connection Attribute, use Tydra API: `GetGeomPrimvar` + /// + /// @param[in] name Primvar name(`primvars:` prefix omitted. e.g. "normals", "st0", ...) + /// @param[out] primvar GeomPrimvar output. + /// @param[out] err Optional Error message(filled when returning false) + /// + bool get_primvar(const std::string &name, GeomPrimvar *primvar, std::string *err = nullptr) const; + + /// + /// Check if primvar exists with given name + /// + /// @param[in] name Primvar name(`primvars:` prefix omitted. e.g. "normals", "st0", ...) + /// + bool has_primvar(const std::string &name) const; + + /// + /// Return List of Primvar in this GPrim contains. + /// + /// NOTE: This API does not support Connection Atttribute(e.g. `int[] primvars:uvs:indices = `) + /// If you want to get Primvar with possible Connection Attribute, use Tydra API: `GetGeomPrimvars` + /// + std::vector get_primvars() const; + + /// + /// Set Attribute(+ indices Attribute for Indexed Primvar) with "primvars:" suffix(namespace) to `props` + /// + /// @param[in] primvar GeomPrimvar + /// @param[out] err Optional Error message(filled when returning false) + /// + /// Returns true when success to add primvar. Return false on error(e.g. `primvar` does not contain valid name). + /// + bool set_primvar(const GeomPrimvar &primvar, std::string *err = nullptr); + + + /// + /// Aux infos + /// + std::vector &primChildrenNames() { + return _primChildrenNames; + } + + std::vector &propertyNames() { + return _propertyNames; + } + + const std::vector &primChildrenNames() const { + return _primChildrenNames; + } + + const std::vector &propertyNames() const { + return _propertyNames; + } + + const std::map &variantSetList() const { + return _variantSetMap; + } + + std::map &variantSetList() { + return _variantSetMap; + } + + // Prim metadataum. + PrimMeta meta; // TODO: Move to private + + const PrimMeta &metas() const { + return meta; + } + + PrimMeta &metas() { + return meta; + } + +#if 0 + // + // NOTE on material binding. + // https://openusd.org/release/wp_usdshade.html + // + // - "all purpose", direct binding, material:binding. single relationship target only + // - a purpose-restricted, direct, fallback binding, e.g. material:binding:preview + // - an all-purpose, collection-based binding, e.g. material:binding:collection:metalBits + // - a purpose-restricted, collection-based binding, e.g. material:binding:collection:full:metalBits + // + // In TinyUSDZ, treat empty purpose token as "all purpose" + // + + bool has_materialBinding() const { + return materialBinding.has_value(); + } + + bool has_materialBindingPreview() const { + return materialBindingPreview.has_value(); + } + + bool has_materialBindingFull() const { + return materialBindingFull.has_value(); + } + + bool has_materialBinding(const value::token &mat_purpose) const { + if (mat_purpose.str() == "full") { + return has_materialBindingFull(); + } else if (mat_purpose.str() == "preview") { + return has_materialBindingPreview(); + } else { + return _materialBindingMap.count(mat_purpose.str()); + } + } + + void clear_materialBinding() { + materialBinding.reset(); + } + + void clear_materialBindingPreview() { + materialBindingPreview.reset(); + } + + void clear_materialBindingFull() { + materialBindingFull.reset(); + } + + void set_materialBinding(const Relationship &rel) { + materialBinding = rel; + } + + void set_materialBinding(const Relationship &rel, const MaterialBindingStrength strength) { + value::token strength_tok(to_string(strength)); + materialBinding = rel; + materialBinding.value().metas().bindMaterialAs = strength_tok; + } + + void set_materialBindingPreview(const Relationship &rel) { + materialBindingPreview = rel; + } + + void set_materialBindingPreview(const Relationship &rel, const MaterialBindingStrength strength) { + value::token strength_tok(to_string(strength)); + materialBindingPreview = rel; + materialBindingPreview.value().metas().bindMaterialAs = strength_tok; + } + + void set_materialBindingFull(const Relationship &rel) { + materialBindingFull = rel; + } + + void set_materialBindingFull(const Relationship &rel, const MaterialBindingStrength strength) { + value::token strength_tok(to_string(strength)); + materialBindingFull = rel; + materialBindingFull.value().metas().bindMaterialAs = strength_tok; + } + + void set_materialBinding(const Relationship &rel, const value::token &mat_purpose) { + + if (mat_purpose.str().empty()) { + return set_materialBinding(rel); + } else if (mat_purpose.str() == "full") { + return set_materialBindingFull(rel); + } else if (mat_purpose.str() == "preview") { + return set_materialBindingFull(rel); + } else { + _materialBindingMap[mat_purpose.str()] = rel; + } + } + + void set_materialBinding(const Relationship &rel, const value::token &mat_purpose, const MaterialBindingStrength strength) { + value::token strength_tok(to_string(strength)); + + if (mat_purpose.str().empty()) { + return set_materialBinding(rel, strength); + } else if (mat_purpose.str() == "full") { + return set_materialBindingFull(rel, strength); + } else if (mat_purpose.str() == "preview") { + return set_materialBindingFull(rel, strength); + } else { + _materialBindingMap[mat_purpose.str()] = rel; + _materialBindingMap[mat_purpose.str()].metas().bindMaterialAs = strength_tok; + } + } + + bool has_materialBindingCollection(const std::string &tok) { + + if (!_materialBindingCollectionMap.count(tok)) { + return false; + } + + return _materialBindingCollectionMap.count(tok); + } + + void set_materialBindingCollection(const value::token &tok, const value::token &mat_purpose, const Relationship &rel) { + + // NOTE: + // https://openusd.org/release/wp_usdshade.html#basic-proposal-for-collection-based-assignment + // says: material:binding:collection defines a namespace of binding relationships to be applied in namespace order, with the earliest ordered binding relationship the strongest + // + // so the app is better first check if `tok` element alreasy exists(using has_materialBindingCollection) + + auto &m = _materialBindingCollectionMap[tok.str()]; + + m[mat_purpose.str()] = rel; + } + + void clear_materialBindingCollection(const value::token &tok, const value::token &mat_purpose) { + if (_materialBindingCollectionMap.count(tok.str())) { + _materialBindingCollectionMap[tok.str()].erase(mat_purpose.str()); + } + } + + void set_materialBindingCollection(const value::token &tok, const value::token &mat_purpose, const Relationship &rel, MaterialBindingStrength strength) { + value::token strength_tok(to_string(strength)); + + _materialBindingCollectionMap[tok.str()][mat_purpose.str()] = rel; + _materialBindingCollectionMap[tok.str()][mat_purpose.str()].metas().bindMaterialAs = strength_tok; + + } + + const std::map> materialBindingCollectionMap() const { + return _materialBindingCollectionMap; + } +#endif + + + private: + + //bool _valid{true}; // default behavior is valid(allow empty GPrim) + + std::vector _primChildrenNames; + std::vector _propertyNames; + + // For Variants + std::map _variantSetMap; + +#if 0 + // For material:binding(excludes frequently used `material:binding`, `material:binding:full` and `material:binding:preview`) + // key = PURPOSE, value = rel + std::map _materialBindingMap; + + // For material:binding:collection + // key = NAME, value = map + // TODO: Use multi-index map + std::map> _materialBindingCollectionMap; +#endif + +}; + +struct Xform : GPrim { + // Xform() {} +}; + +// GeomSubset +struct GeomSubset : public MaterialBinding, Collection { + enum class ElementType { Face, Point }; + + enum class FamilyType { + Partition, // 'partition' + NonOverlapping, // 'nonOverlapping' + Unrestricted, // 'unrestricted' (fallback) + }; + + std::string name; + Specifier spec{Specifier::Def}; + + int64_t parent_id{-1}; // Index to parent node + + TypedAttributeWithFallback elementType{ElementType::Face}; + TypedAttribute familyName; // "uniform token familyName" + + // FamilyType attribute is described in parent GeomMesh's `subsetFamily::familyType` attribute. + //TypedAttributeWithFallback familyType{FamilyType::Unrestricted}; + + nonstd::expected SetElementType(const std::string &str) { + if (str == "face") { + elementType = ElementType::Face; + return true; + } else if (str == "point") { + elementType = ElementType::Point; + return true; + } + + return nonstd::make_unexpected( + "`face` or `point` is supported for `elementType`, but `" + str + + "` specified"); + } + +#if 0 + // Some frequently used materialBindings + nonstd::optional materialBinding; // rel material:binding + nonstd::optional materialBindingCollection; // rel material:binding:collection + nonstd::optional materialBindingPreview; // rel material:binding:preview +#endif + + TypedAttribute>> indices; // int[] indices + + std::map props; // custom Properties + PrimMeta meta; + + std::vector &primChildrenNames() { + return _primChildrenNames; + } + + std::vector &propertyNames() { + return _propertyNames; + } + + const std::vector &primChildrenNames() const { + return _primChildrenNames; + } + + const std::vector &propertyNames() const { + return _propertyNames; + } + + static bool ValidateSubsets( + const std::vector &subsets, + const size_t elementCount, + const FamilyType &familyType, std::string *err); + + + private: + std::vector _primChildrenNames; + std::vector _propertyNames; +}; + +// Polygon mesh geometry +// X11's X.h uses `None` macro, so add extra prefix to `None` enum +struct GeomMesh : GPrim { + enum class InterpolateBoundary { + InterpolateBoundaryNone, // "none" + EdgeAndCorner, // "edgeAndCorner" + EdgeOnly // "edgeOnly" + }; + + enum class FaceVaryingLinearInterpolation { + CornersPlus1, // "cornersPlus1" + CornersPlus2, // "cornersPlus2" + CornersOnly, // "cornersOnly" + Boundaries, // "boundaries" + FaceVaryingLinearInterpolationNone, // "none" + All, // "all" + }; + + enum class SubdivisionScheme { + CatmullClark, // "catmullClark" + Loop, // "loop" + Bilinear, // "bilinear" + SubdivisionSchemeNone, // "none" + }; + + // + // Predefined attribs. + // + TypedAttribute>> points; // point3f[] + TypedAttribute>> + normals; // normal3f[] (NOTE: "primvars:normals" are stored in + // `GPrim::props`) + + TypedAttribute>> + velocities; // vector3f[] + + TypedAttribute>> + faceVertexCounts; // int[] faceVertexCounts + TypedAttribute>> + faceVertexIndices; // int[] faceVertexIndices + + // Make SkelBindingAPI first citizen. + nonstd::optional skeleton; // rel skel:skeleton + + // + // Utility functions + // + + + /// + /// @brief Returns `points`. + /// + /// NOTE: No support for connected attribute. Using tydra::EvaluateTypedAttribute preferred. + /// + /// @param[in] time Time for TimeSampled `points` data. + /// @param[in] interp Interpolation type for TimeSampled `points` data + /// @return points vector(copied). Returns empty when `points` attribute is + /// not defined. + /// + const std::vector get_points( + double time = value::TimeCode::Default(), + value::TimeSampleInterpolationType interp = + value::TimeSampleInterpolationType::Linear) const; + + /// + /// @brief Returns normals vector. Precedence order: `primvars:normals` then + /// `normals`. + /// + /// NOTE: No support for connected attribute. Using tydra::GetGeomPrimvar preferred. + /// + /// @return normals vector(copied). Returns empty normals vector when neither + /// `primvars:normals` nor `normals` attribute defined, attribute is a + /// Relationship, Connection Attribute, or normals attribute have invalid type(other than `normal3f`). + /// + const std::vector get_normals( + double time = value::TimeCode::Default(), + value::TimeSampleInterpolationType interp = + value::TimeSampleInterpolationType::Linear) const; + + /// + /// @brief Get interpolation of `primvars:normals`, then `normals`. + /// @return Interpolation of normals. `vertex` by defaut. + /// + Interpolation get_normalsInterpolation() const; + + /// + /// @brief Returns `faceVertexCounts`. + /// + /// NOTE: No support for connected attribute. Using tydra::EvaluateTypedAttribute preferred. + /// + /// @return face vertex counts vector(copied) + /// + const std::vector get_faceVertexCounts() const; + + /// + /// @brief Returns `faceVertexIndices`. + /// + /// @return face vertex indices vector(copied) + /// + const std::vector get_faceVertexIndices() const; + + // + // SubD attribs. + // + TypedAttribute>> + cornerIndices; // int[] cornerIndices + TypedAttribute>> + cornerSharpnesses; // float[] cornerSharpnesses + TypedAttribute>> + creaseIndices; // int[] creaseIndices + TypedAttribute>> + creaseLengths; // int[] creaseLengths + TypedAttribute>> + creaseSharpnesses; // float[] creaseSharpnesses + TypedAttribute>> + holeIndices; // int[] holeIndices + TypedAttributeWithFallback> + interpolateBoundary{ + InterpolateBoundary::EdgeAndCorner}; // token interpolateBoundary + TypedAttributeWithFallback subdivisionScheme{ + SubdivisionScheme::CatmullClark}; // uniform token subdivisionScheme + TypedAttributeWithFallback> + faceVaryingLinearInterpolation{ + FaceVaryingLinearInterpolation:: + CornersPlus1}; // token faceVaryingLinearInterpolation + + TypedAttribute> blendShapes; // uniform token[] skel:blendShapes + nonstd::optional blendShapeTargets; // rel skel:blendShapeTargets (Path[]) + + // + // TODO: Make these primvars first citizen? + // - int[] primvars:skel:jointIndices + // - float[] primvars:skel:jointWeights + + + /// + /// For GeomSubset + /// + /// This creates `uniform token subsetFamily::familyType = familyType` attribute when serialized. + /// + void set_subsetFamilyType(const value::token &familyName, GeomSubset::FamilyType familyType) { + subsetFamilyTypeMap[familyName] = familyType; + } + + /// + /// This look ups `uniform token subsetFamily::familyType = familyType` attribute. + /// + /// @return true upon success, false when corresponding attribute not found or invalid. + bool get_subsetFamilyType(const value::token &familyName, GeomSubset::FamilyType *familyType) { + if (!familyType) { + return false; + } + + if (subsetFamilyTypeMap.count(familyName)) { + (*familyType) = subsetFamilyTypeMap[familyName]; + return true; + } + return false; + + } + + /// + /// Return the list of subet familyNames in this GeomMesh. + /// + /// This lists `uniform token subsetFamily::familyType` attributes. + /// + /// @return The list familyNames. Empty when no familyName attribute found. + std::vector get_subsetFamilyNames() { + std::vector toks; + for (const auto &item : subsetFamilyTypeMap) { + toks.push_back(item.first); + } + return toks; + } + + + // familyName -> familyType map + std::map subsetFamilyTypeMap; + +#if 0 // GeomSubset Prim is now managed as a child Prim + // + // GeomSubset + // + // uniform token `subsetFamily:materialBind:familyType` + GeomSubset::FamilyType materialBindFamilyType{ + GeomSubset::FamilyType::Partition}; + + std::vector geom_subset_children; + +#endif + +#if 0 // Deprecated: Use tydra::GetGeomSubsets() instead. + /// + /// Get GeomSubset list assgied to this GeomMesh(child Prim). + /// + /// The pointer points to the address of child Prim, + /// so should not free it and this GeomMesh object must be valid during using the pointer to GeomSubset. + /// + std::vector GetGeomSubsets(); +#endif + +}; + +struct GeomCamera : public GPrim { + enum class Projection { + Perspective, // "perspective" + Orthographic, // "orthographic" + }; + + enum class StereoRole { + Mono, // "mono" + Left, // "left" + Right, // "right" + }; + + // + // Properties + // + // NOTE: fallback value is in [mm](tenth of scene unit) + // + + TypedAttribute>> clippingPlanes; // float4[] + TypedAttributeWithFallback> clippingRange{ + value::float2({0.1f, 1000000.0f})}; + TypedAttributeWithFallback> exposure{0.0f}; // in EV + TypedAttributeWithFallback> focalLength{50.0f}; + TypedAttributeWithFallback> focusDistance{0.0f}; + TypedAttributeWithFallback> horizontalAperture{20.965f}; + TypedAttributeWithFallback> horizontalApertureOffset{0.0f}; + TypedAttributeWithFallback> verticalAperture{15.2908f}; + TypedAttributeWithFallback> verticalApertureOffset{0.0f}; + TypedAttributeWithFallback> fStop{ + 0.0f}; // 0.0 = no focusing + TypedAttributeWithFallback> projection{ + Projection::Perspective}; // "token projection" Animatable + + TypedAttributeWithFallback stereoRole{ + StereoRole::Mono}; // "uniform token stereoRole" + + TypedAttributeWithFallback> shutterClose{ + 0.0}; // double shutter:close + TypedAttributeWithFallback> shutterOpen{ + 0.0}; // double shutter:open +}; + +// struct GeomBoundable : GPrim {}; + +struct GeomCone : public GPrim { + // + // Properties + // + TypedAttributeWithFallback> height{2.0}; + TypedAttributeWithFallback> radius{1.0}; + + TypedAttributeWithFallback axis{Axis::Z}; +}; + +struct GeomCapsule : public GPrim { + // + // Properties + // + TypedAttributeWithFallback> height{2.0}; + TypedAttributeWithFallback> radius{0.5}; + TypedAttributeWithFallback axis{Axis::Z}; // uniform token axis +}; + +struct GeomCylinder : public GPrim { + // + // Properties + // + TypedAttributeWithFallback> height{2.0}; + TypedAttributeWithFallback> radius{1.0}; + TypedAttributeWithFallback axis{Axis::Z}; // uniform token axis +}; + +struct GeomCube : public GPrim { + // + // Properties + // + TypedAttributeWithFallback> size{2.0}; +}; + +struct GeomSphere : public GPrim { + // + // Predefined attribs. + // + TypedAttributeWithFallback> radius{2.0}; +}; + +// +// Basis Curves(for hair/fur) +// +struct GeomBasisCurves : public GPrim { + enum class Type { + Cubic, // "cubic"(default) + Linear, // "linear" + }; + + enum class Basis { + Bezier, // "bezier"(default) + Bspline, // "bspline" + CatmullRom, // "catmullRom" + }; + + enum class Wrap { + Nonperiodic, // "nonperiodic"(default) + Periodic, // "periodic" + Pinned, // "pinned" + }; + + TypedAttributeWithFallback type{Type::Cubic}; + TypedAttributeWithFallback basis{Basis::Bezier}; + TypedAttributeWithFallback wrap{Wrap::Nonperiodic}; + + // + // Predefined attribs. + // + TypedAttribute>> points; // point3f + TypedAttribute>> normals; // normal3f + TypedAttribute>> curveVertexCounts; + TypedAttribute>> widths; + TypedAttribute>> + velocities; // vector3f + TypedAttribute>> + accelerations; // vector3f +}; + +struct GeomNurbsCurves : public GPrim { + + // + // Predefined attribs. + // + TypedAttribute>> + accelerations; + TypedAttribute>> + velocities; + TypedAttribute>> + curveVertexCounts; + TypedAttribute>> + normals; + TypedAttribute>> + points; + TypedAttribute>> + widths; + + + TypedAttribute>> order; + TypedAttribute>> knots; + TypedAttribute>> ranges; + TypedAttribute>> pointWeights; +}; + +// +// Points primitive. +// +struct GeomPoints : public GPrim { + // + // Predefined attribs. + // + TypedAttribute>> points; // point3f[] + TypedAttribute>> + normals; // normal3f[] + TypedAttribute>> widths; // float[] + TypedAttribute>> + ids; // int64[] per-point ids. + TypedAttribute>> + velocities; // vector3f[] + TypedAttribute>> + accelerations; // vector3f[] +}; + +// +// Point instancer(TODO). +// +struct PointInstancer : public GPrim { + nonstd::optional prototypes; // rel prototypes + + TypedAttribute>> + protoIndices; // int[] protoIndices + TypedAttribute>> ids; // int64[] ids + TypedAttribute>> + positions; // point3f[] positions + TypedAttribute>> + orientations; // quath[] orientations + TypedAttribute>> + scales; // float3[] scales + TypedAttribute>> + velocities; // vector3f[] velocities + TypedAttribute>> + accelerations; // vector3f[] accelerations + TypedAttribute>> + angularVelocities; // vector3f[] angularVelocities + TypedAttribute>> + invisibleIds; // int64[] invisibleIds +}; + + +// import DEFINE_TYPE_TRAIT and DEFINE_ROLE_TYPE_TRAIT +#include "define-type-trait.inc" + +namespace value { + +// Geom +DEFINE_TYPE_TRAIT(GPrim, kGPrim, TYPE_ID_GPRIM, 1); + +DEFINE_TYPE_TRAIT(Xform, kGeomXform, TYPE_ID_GEOM_XFORM, 1); +DEFINE_TYPE_TRAIT(GeomMesh, kGeomMesh, TYPE_ID_GEOM_MESH, 1); +DEFINE_TYPE_TRAIT(GeomBasisCurves, kGeomBasisCurves, TYPE_ID_GEOM_BASIS_CURVES, + 1); +DEFINE_TYPE_TRAIT(GeomNurbsCurves, kGeomNurbsCurves, TYPE_ID_GEOM_NURBS_CURVES, + 1); +DEFINE_TYPE_TRAIT(GeomSphere, kGeomSphere, TYPE_ID_GEOM_SPHERE, 1); +DEFINE_TYPE_TRAIT(GeomCube, kGeomCube, TYPE_ID_GEOM_CUBE, 1); +DEFINE_TYPE_TRAIT(GeomCone, kGeomCone, TYPE_ID_GEOM_CONE, 1); +DEFINE_TYPE_TRAIT(GeomCylinder, kGeomCylinder, TYPE_ID_GEOM_CYLINDER, 1); +DEFINE_TYPE_TRAIT(GeomCapsule, kGeomCapsule, TYPE_ID_GEOM_CAPSULE, 1); +DEFINE_TYPE_TRAIT(GeomPoints, kGeomPoints, TYPE_ID_GEOM_POINTS, 1); +DEFINE_TYPE_TRAIT(GeomSubset, kGeomSubset, TYPE_ID_GEOM_GEOMSUBSET, 1); +DEFINE_TYPE_TRAIT(GeomCamera, kGeomCamera, TYPE_ID_GEOM_CAMERA, 1); +DEFINE_TYPE_TRAIT(PointInstancer, kPointInstancer, TYPE_ID_GEOM_POINT_INSTANCER, + 1); + +#undef DEFINE_TYPE_TRAIT +#undef DEFINE_ROLE_TYPE_TRAIT + +} // namespace value + +// Relation is supported as geomprimvar. +// example: +// +// rel primvar:myrel = [, ] +// + +// NOTE: `bool` type seems not supported on pxrUSD +// NOTE: `string` type need special treatment when `idFrom` Relationship exists( https://github.com/syoyo/tinyusdz/issues/113 ) +#define APPLY_GEOMPRIVAR_TYPE(__FUNC) \ + __FUNC(bool) \ + __FUNC(std::string) \ + __FUNC(value::half) \ + __FUNC(value::half2) \ + __FUNC(value::half3) \ + __FUNC(value::half4) \ + __FUNC(int) \ + __FUNC(value::int2) \ + __FUNC(value::int3) \ + __FUNC(value::int4) \ + __FUNC(uint32_t) \ + __FUNC(value::uint2) \ + __FUNC(value::uint3) \ + __FUNC(value::uint4) \ + __FUNC(float) \ + __FUNC(value::float2) \ + __FUNC(value::float3) \ + __FUNC(value::float4) \ + __FUNC(double) \ + __FUNC(value::double2) \ + __FUNC(value::double3) \ + __FUNC(value::double4) \ + __FUNC(value::matrix2d) \ + __FUNC(value::matrix3d) \ + __FUNC(value::matrix4d) \ + __FUNC(value::quath) \ + __FUNC(value::quatf) \ + __FUNC(value::quatd) \ + __FUNC(value::normal3h) \ + __FUNC(value::normal3f) \ + __FUNC(value::normal3d) \ + __FUNC(value::vector3h) \ + __FUNC(value::vector3f) \ + __FUNC(value::vector3d) \ + __FUNC(value::point3h) \ + __FUNC(value::point3f) \ + __FUNC(value::point3d) \ + __FUNC(value::color3f) \ + __FUNC(value::color3d) \ + __FUNC(value::color4f) \ + __FUNC(value::color4d) \ + __FUNC(value::texcoord2h) \ + __FUNC(value::texcoord2f) \ + __FUNC(value::texcoord2d) \ + __FUNC(value::texcoord3h) \ + __FUNC(value::texcoord3f) \ + __FUNC(value::texcoord3d) + +// TODO: 64bit int/uint seems not supported on pxrUSD. Enable it in TinyUSDZ? +#if 0 + __FUNC(int64_t) \ + __FUNC(uint64_t) +#endif + +#define EXTERN_TEMPLATE_GET_VALUE(__ty) \ + extern template bool GeomPrimvar::get_value(__ty *dest, std::string *err) const; \ + extern template bool GeomPrimvar::get_value(double, __ty *dest, value::TimeSampleInterpolationType, std::string *err) const; \ + extern template bool GeomPrimvar::get_value(std::vector<__ty> *dest, std::string *err) const; \ + extern template bool GeomPrimvar::get_value(double, std::vector<__ty> *dest, value::TimeSampleInterpolationType, std::string *err) const; \ + extern template bool GeomPrimvar::flatten_with_indices(std::vector<__ty> *dest, std::string *err) const; \ + extern template bool GeomPrimvar::flatten_with_indices(double, std::vector<__ty> *dest, value::TimeSampleInterpolationType, std::string *err) const; + +APPLY_GEOMPRIVAR_TYPE(EXTERN_TEMPLATE_GET_VALUE) + +#undef EXTERN_TEMPLATE_GET_VALUE + +//#undef APPLY_GEOMPRIVAR_TYPE + + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usdLux.cc b/contrib/tinyusdz/tinyusdz_repo/src/usdLux.cc new file mode 100644 index 000000000..cbd37e5ad --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usdLux.cc @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - Present, Syoyo Fujita. +// +// UsdLux implementations + +#include "usdLux.hh" + +namespace tinyusdz { + +} // namespace tinyusdz + + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usdLux.hh b/contrib/tinyusdz/tinyusdz_repo/src/usdLux.hh new file mode 100644 index 000000000..fcec3cebd --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usdLux.hh @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +// UsdLux LightSource +#pragma once + +#include "prim-types.hh" +#include "xform.hh" + +namespace tinyusdz { + +constexpr auto kSphereLight = "SphereLight"; +constexpr auto kCylinderLight = "CylinderLight"; +constexpr auto kDomeLight = "DomeLight"; +constexpr auto kDiskLight = "DiskLight"; +constexpr auto kRectLight = "RectLight"; +constexpr auto kDistantLight = "DistantLight"; +constexpr auto kGeometryLight = "GeometryLight"; +constexpr auto kPortalLight = "PortalLight"; +constexpr auto kPluginLight = "PluginLight"; + +class BoundableLight : public Xformable, public Collection { + + public: + std::string name; + Specifier spec{Specifier::Def}; + int64_t parent_id{-1}; // Index to xform node + + TypedAttribute> extent; // float3[] + TypedAttributeWithFallback> visibility{Visibility::Inherited}; + TypedAttributeWithFallback purpose{Purpose::Default}; + + // Light API + TypedAttributeWithFallback> color{value::color3f({1.0f, 1.0f, 1.0f})}; // inputs.color Light energy in linear color space. + TypedAttributeWithFallback> colorTemperature{6500.0f}; // inputs:colorTemperature + TypedAttributeWithFallback> diffuse{1.0f}; // inputs:diffuse diffuse multiplier + TypedAttributeWithFallback> enableColorTemperature{false}; // inputs:enableColorTemperature + TypedAttributeWithFallback> exposure{0.0f}; // inputs:exposure EV + TypedAttributeWithFallback> intensity{1.0f}; // inputs:intensity + TypedAttributeWithFallback> normalize{false}; // inputs:normalize normalize power by the surface area of the light. + TypedAttributeWithFallback> specular{1.0f}; // inputs:specular specular multiplier + // rel light:filters + + + std::pair> references; + std::pair> payload; + std::map variantSet; + std::map props; + PrimMeta meta; // TODO: move to private + + const PrimMeta &metas() const { return meta; } + PrimMeta &metas() { return meta; } + + const std::vector &primChildrenNames() const { return _primChildren; } + const std::vector &propertyNames() const { return _properties; } + std::vector &primChildrenNames() { return _primChildren; } + std::vector &propertyNames() { return _properties; } + + private: + + std::vector _primChildren; + std::vector _properties; +}; + +// non-boundable still inherits Xformable. +// Differences with boundable is just `extent` attribute is omitted. +class NonboundableLight : public Xformable, public Collection { + + public: + std::string name; + Specifier spec{Specifier::Def}; + int64_t parent_id{-1}; // Index to xform node + + TypedAttributeWithFallback> visibility{Visibility::Inherited}; + TypedAttributeWithFallback purpose{Purpose::Default}; + + // Light API + TypedAttributeWithFallback> color{value::color3f({1.0f, 1.0f, 1.0f})}; // inputs.color Light energy in linear color space. + TypedAttributeWithFallback> colorTemperature{6500.0f}; // inputs:colorTemperature + TypedAttributeWithFallback> diffuse{1.0f}; // inputs:diffuse diffuse multiplier + TypedAttributeWithFallback> enableColorTemperature{false}; // inputs:enableColorTemperature + TypedAttributeWithFallback> exposure{0.0f}; // inputs:exposure EV + TypedAttributeWithFallback> intensity{1.0f}; // inputs:intensity + TypedAttributeWithFallback> normalize{false}; // inputs:normalize normalize power by the surface area of the light. + TypedAttributeWithFallback> specular{1.0f}; // inputs:specular specular multiplier + // rel light:filters + + + std::pair> references; + std::pair> payload; + std::map variantSet; + std::map props; + PrimMeta meta; // TODO: move to private + + const PrimMeta &metas() const { return meta; } + PrimMeta &metas() { return meta; } + + const std::vector &primChildrenNames() const { return _primChildren; } + const std::vector &propertyNames() const { return _properties; } + std::vector &primChildrenNames() { return _primChildren; } + std::vector &propertyNames() { return _properties; } + + private: + + std::vector _primChildren; + std::vector _properties; +}; + +struct SphereLight : public BoundableLight { + + TypedAttributeWithFallback> radius{0.5f}; // inputs:radius + +}; + +struct CylinderLight : public BoundableLight { + + TypedAttributeWithFallback> length{1.0f}; // inputs:length size in Y axis + TypedAttributeWithFallback> radius{0.5f}; // inputs:radius size in X axis + +}; + + +struct RectLight : public BoundableLight { + + TypedAttribute> file; // asset inputs:texture:file + TypedAttributeWithFallback> height{1.0f}; // inputs:height size in Y axis + TypedAttributeWithFallback> width{1.0f}; // inputs:width size in X axis + +}; + +struct DiskLight : public BoundableLight { + + TypedAttributeWithFallback> radius{0.5f}; // inputs:radius + +}; + +// NOTE: Make Distance xformable? +struct DistantLight : public NonboundableLight { + + TypedAttributeWithFallback> angle{0.53f}; // inputs:angle in degrees + +}; + +struct DomeLight : public NonboundableLight { + + enum class TextureFormat { + Automatic, // "automatic" + Latlong, // "latlong" + MirroredBall, // "mirroredBall" + Angular // "angular" + }; + + // DomeLight specific + TypedAttributeWithFallback> guideRadius{1.0e5f}; + TypedAttribute> file; // asset inputs:texture:file + TypedAttributeWithFallback> textureFormat{TextureFormat::Automatic}; // token inputs:texture:format + // rel portals + // rel proxyPrim + +}; + +// TODO: Deprecate +struct GeometryLight : public NonboundableLight { + + RelationshipProperty geometry; // `rel geometry` + +}; + +// TODO +struct PortalLight : public NonboundableLight { + +}; + +// TODO +struct PluginLight : public Xformable, public Collection { +}; + +#if 0 // TODO +struct PluginLightFilter : public Light { +}; +#endif + +inline bool IsLightPrim(const Prim &prim) { + return (prim.type_id() > value::TYPE_ID_LUX_BEGIN) && (prim.type_id() < value::TYPE_ID_LUX_END); +} + +// import DEFINE_TYPE_TRAIT and DEFINE_ROLE_TYPE_TRAIT +#include "define-type-trait.inc" + +namespace value { + +DEFINE_TYPE_TRAIT(DomeLight, kDomeLight, TYPE_ID_LUX_DOME, 1); +DEFINE_TYPE_TRAIT(CylinderLight, kCylinderLight, TYPE_ID_LUX_CYLINDER, 1); +DEFINE_TYPE_TRAIT(SphereLight, kSphereLight, TYPE_ID_LUX_SPHERE, 1); +DEFINE_TYPE_TRAIT(DiskLight, kDiskLight, TYPE_ID_LUX_DISK, 1); +DEFINE_TYPE_TRAIT(DistantLight, kDistantLight, TYPE_ID_LUX_DISTANT, 1); +DEFINE_TYPE_TRAIT(RectLight, kRectLight, TYPE_ID_LUX_RECT, 1); +DEFINE_TYPE_TRAIT(GeometryLight, kGeometryLight, TYPE_ID_LUX_GEOMETRY, 1); +DEFINE_TYPE_TRAIT(PortalLight, kPortalLight, TYPE_ID_LUX_PORTAL, 1); +DEFINE_TYPE_TRAIT(PluginLight, kPluginLight, TYPE_ID_LUX_PLUGIN, 1); + +#undef DEFINE_TYPE_TRAIT +#undef DEFINE_ROLE_TYPE_TRAIT + +} // namespace value + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usdMtlx.cc b/contrib/tinyusdz/tinyusdz_repo/src/usdMtlx.cc new file mode 100644 index 000000000..c678d7ea3 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usdMtlx.cc @@ -0,0 +1,1084 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2023 - Present, Light Transport Entertainment, Inc. + +#if defined(TINYUSDZ_USE_USDMTLX) + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#include "external/pugixml.hpp" +// #include "external/jsonhpp/nlohmann/json.hpp" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#endif // TINYUSDZ_USE_USDMTLX + +#include + +#include "usdMtlx.hh" + +#if defined(TINYUSDZ_USE_USDMTLX) + +#include "ascii-parser.hh" // To parse color3f value +#include "common-macros.inc" +#include "external/dtoa_milo.h" +#include "io-util.hh" +#include "pprinter.hh" +#include "tiny-format.hh" +#include "value-pprint.hh" + +inline std::string dtos(const double v) { + char buf[128]; + dtoa_milo(v, buf); + + return std::string(buf); +} + +#define PushWarn(msg) \ + do { \ + if (warn) { \ + (*warn) += msg; \ + } \ + } while (0); + +#define PushError(msg) \ + do { \ + if (err) { \ + (*err) += msg; \ + } \ + } while (0); + +namespace tinyusdz { + +// defined in ascii-parser-base-types.cc +namespace ascii { + +extern template bool AsciiParser::SepBy1BasicType( + const char sep, std::vector *ret); + +} // namespace ascii + +namespace detail { + +template +Property MakeProperty(const T &value) { + Attribute attr(value); + Property prop(attr, /* custom */ false); + + return prop; +} + +bool is_supported_type(const std::string &typeName); + +bool is_supported_type(const std::string &typeName) { + if (typeName.compare("integer") == 0) return true; + if (typeName.compare("boolean") == 0) return true; + if (typeName.compare("float") == 0) return true; + if (typeName.compare("color3") == 0) return true; + if (typeName.compare("color4") == 0) return true; + if (typeName.compare("vector2") == 0) return true; + if (typeName.compare("vector3") == 0) return true; + if (typeName.compare("vector4") == 0) return true; + if (typeName.compare("matrix33") == 0) return true; + if (typeName.compare("matrix44") == 0) return true; + if (typeName.compare("string") == 0) return true; + if (typeName.compare("filename") == 0) return true; + + if (typeName.compare("integerarray") == 0) return true; + if (typeName.compare("floatarray") == 0) return true; + if (typeName.compare("vector2array") == 0) return true; + if (typeName.compare("vector3array") == 0) return true; + if (typeName.compare("vector4array") == 0) return true; + if (typeName.compare("color3array") == 0) return true; + if (typeName.compare("color4array") == 0) return true; + if (typeName.compare("stringarray") == 0) return true; + + // No matrixarray + + // TODO + // if (typeName.compare("color") == 0) return true; + // if (typeName.compare("geomname") == 0) return true; + // if (typeName.compare("geomnamearray") == 0) return true; + + return false; +} + +template +bool ParseValue(tinyusdz::ascii::AsciiParser &parser, T &ret, std::string *err); + +template <> +bool ParseValue(tinyusdz::ascii::AsciiParser &parser, int &ret, + std::string *err) { + int val; + if (!parser.ReadBasicType(&val)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to parse a value of type `{}`", + value::TypeTraits::type_name())); + } + + ret = val; + + return true; +} + +template <> +bool ParseValue(tinyusdz::ascii::AsciiParser &parser, bool &ret, + std::string *err) { + bool val; + if (!parser.ReadBasicType(&val)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to parse a value of type `{}`", + value::TypeTraits::type_name())); + } + + ret = val; + + return true; +} + +template <> +bool ParseValue(tinyusdz::ascii::AsciiParser &parser, float &ret, + std::string *err) { + float val; + if (!parser.ReadBasicType(&val)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to parse a value of type `{}`", + value::TypeTraits::type_name())); + } + + ret = val; + + return true; +} + +template <> +bool ParseValue(tinyusdz::ascii::AsciiParser &parser, + std::string &ret, std::string *err) { + std::string val; + if (!parser.ReadBasicType(&val)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to parse a value of type `{}`", + value::TypeTraits::type_name())); + } + + ret = val; + + return true; +} + +template <> +bool ParseValue(tinyusdz::ascii::AsciiParser &parser, + value::float2 &ret, std::string *err) { + std::vector values; + if (!parser.SepBy1BasicType(',', &values)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to parse a value of type `{}`", + value::TypeTraits::type_name())); + } + + if (values.size() != 2) { + PUSH_ERROR_AND_RETURN(fmt::format( + "type `{}` expects the number of elements is 2, but got {}", + value::TypeTraits::type_name(), values.size())); + } + + ret[0] = values[0]; + ret[1] = values[1]; + + return true; +} + +template <> +bool ParseValue(tinyusdz::ascii::AsciiParser &parser, + value::float3 &ret, std::string *err) { + std::vector values; + if (!parser.SepBy1BasicType(',', &values)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to parse a value of type `{}`", + value::TypeTraits::type_name())); + } + + if (values.size() != 3) { + PUSH_ERROR_AND_RETURN(fmt::format( + "type `{}` expects the number of elements is 3, but got {}", + value::TypeTraits::type_name(), values.size())); + } + + ret[0] = values[0]; + ret[1] = values[1]; + ret[2] = values[2]; + + return true; +} + +template <> +bool ParseValue(tinyusdz::ascii::AsciiParser &parser, + value::vector3f &ret, std::string *err) { + std::vector values; + if (!parser.SepBy1BasicType(',', &values)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to parse a value of type `{}`", + value::TypeTraits::type_name())); + } + + if (values.size() != 3) { + PUSH_ERROR_AND_RETURN(fmt::format( + "type `{}` expects the number of elements is 3, but got {}", + value::TypeTraits::type_name(), values.size())); + } + + ret[0] = values[0]; + ret[1] = values[1]; + ret[2] = values[2]; + + return true; +} + +template <> +bool ParseValue(tinyusdz::ascii::AsciiParser &parser, + value::normal3f &ret, std::string *err) { + std::vector values; + if (!parser.SepBy1BasicType(',', &values)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to parse a value of type `{}`", + value::TypeTraits::type_name())); + } + + if (values.size() != 3) { + PUSH_ERROR_AND_RETURN(fmt::format( + "type `{}` expects the number of elements is 3, but got {}", + value::TypeTraits::type_name(), values.size())); + } + + ret[0] = values[0]; + ret[1] = values[1]; + ret[2] = values[2]; + + return true; +} + + +template <> +bool ParseValue(tinyusdz::ascii::AsciiParser &parser, + value::color3f &ret, std::string *err) { + std::vector values; + if (!parser.SepBy1BasicType(',', &values)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to parse a value of type `{}`", + value::TypeTraits::type_name())); + } + + if (values.size() != 3) { + PUSH_ERROR_AND_RETURN(fmt::format( + "type `{}` expects the number of elements is 3, but got {}", + value::TypeTraits::type_name(), values.size())); + } + + ret[0] = values[0]; + ret[1] = values[1]; + ret[2] = values[2]; + + return true; +} + +template <> +bool ParseValue(tinyusdz::ascii::AsciiParser &parser, + value::float4 &ret, std::string *err) { + std::vector values; + if (!parser.SepBy1BasicType(',', &values)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to parse a value of type `{}`", + value::TypeTraits::type_name())); + } + + if (values.size() != 4) { + PUSH_ERROR_AND_RETURN(fmt::format( + "type `{}` expects the number of elements is 4, but got {}", + value::TypeTraits::type_name(), values.size())); + } + + ret[0] = values[0]; + ret[1] = values[1]; + ret[2] = values[2]; + ret[3] = values[3]; + + return true; +} + +/// +/// For MaterialX XML. +/// Parse string representation of Attribute value. +/// e.g. "0.0, 1.1" for vector2 type +/// NOTE: no parenthesis('(', '[') for vector and array type. +/// +/// @param[in] typeName typeName(e.g. "vector2") +/// @param[in] str Ascii representation of value. +/// @param[out] value Ascii representation of value. +/// @param[out] err Parse error message when returning false. +/// +/// +/// Supported data type: boolean, float, color3, color4, vector2, vector3, +/// vector4, matrix33, matrix44, string, filename, integerarray, floatarray, +/// color3array, color4array, vector2array, vector3array, vector4array, +/// stringarray. Unsupported data type: geomname, geomnamearray +/// +bool ParseMaterialXValue(const std::string &typeName, const std::string &str, + value::Value *value, std::string *err); + +bool ParseMaterialXValue(const std::string &typeName, const std::string &str, + value::Value *value, std::string *err) { + (void)value; + + if (!is_supported_type(typeName)) { + PUSH_ERROR_AND_RETURN( + fmt::format("Invalid/unsupported type: {}", typeName)); + } + + tinyusdz::StreamReader sr(reinterpret_cast(str.data()), + str.size(), /* swap endian */ false); + tinyusdz::ascii::AsciiParser parser(&sr); + + if (typeName.compare("integer") == 0) { + int val; + if (!ParseValue(parser, val, err)) { + return false; + } + } else if (typeName.compare("boolean") == 0) { + bool val; + if (!ParseValue(parser, val, err)) { + return false; + } + } else if (typeName.compare("vector2") == 0) { + value::float2 val; + if (!ParseValue(parser, val, err)) { + return false; + } + } else if (typeName.compare("vector3") == 0) { + value::float3 val; + if (!ParseValue(parser, val, err)) { + return false; + } + } else if (typeName.compare("vector4") == 0) { + value::float4 val; + if (!ParseValue(parser, val, err)) { + return false; + } + } else { + PUSH_ERROR_AND_RETURN("TODO: " + typeName); + } + + // TODO + return false; +} + +template +bool ParseMaterialXValue(const std::string &str, T *value, std::string *err) { + tinyusdz::StreamReader sr(reinterpret_cast(str.data()), + str.size(), /* swap endian */ false); + tinyusdz::ascii::AsciiParser parser(&sr); + + T val; + + if (!ParseValue(parser, val, err)) { + return false; + } + + (*value) = val; + return true; +} + +template +std::string to_xml_string(const T &val); + +template <> +std::string to_xml_string(const float &val) { + return dtos(double(val)); +} + +template <> +std::string to_xml_string(const int &val) { + return std::to_string(val); +} + +template <> +std::string to_xml_string(const value::color3f &val) { + return dtos(double(val.r)) + ", " + dtos(double(val.g)) + ", " + + dtos(double(val.b)); +} + +template <> +std::string to_xml_string(const value::normal3f &val) { + return dtos(double(val.x)) + ", " + dtos(double(val.y)) + ", " + + dtos(double(val.z)); +} + +template +bool SerializeAttribute(const std::string &attr_name, + const TypedAttributeWithFallback> &attr, + std::string &value_str, std::string *err) { + std::stringstream value_ss; + + if (attr.is_connection()) { + PUSH_ERROR_AND_RETURN(fmt::format("TODO: connection attribute")); + } else if (attr.is_blocked()) { + // do nothing + value_str = ""; + return true; + } else { + const Animatable &animatable_value = attr.get_value(); + if (animatable_value.is_scalar()) { + T value; + if (animatable_value.get_scalar(&value)) { + value_ss << "\"" << to_xml_string(value) << "\""; + } else { + PUSH_ERROR_AND_RETURN(fmt::format( + "Failed to get the value at default time of `{}`", attr_name)); + } + } else if (animatable_value.is_timesamples()) { + // no time-varying attribute in MaterialX. Use the value at default + // timecode. + T value; + if (animatable_value.get(value::TimeCode::Default(), &value)) { + value_ss << "\"" << to_xml_string(value) << "\""; + } else { + PUSH_ERROR_AND_RETURN(fmt::format( + "Failed to get the value at default time of `{}`", attr_name)); + } + } else { + PUSH_ERROR_AND_RETURN( + fmt::format("Failed to get the value of `{}`", attr_name)); + } + } + + value_str = value_ss.str(); + return true; +} + +static bool WriteMaterialXToString(const MtlxUsdPreviewSurface &shader, + std::string &xml_str, std::string *warn, + std::string *err) { + (void)warn; + + // We directly write xml string for simplicity. + // + // TODO: + // - [ ] Use pugixml to write xml string. + + std::stringstream ss; + + std::string node_name = "SR_default"; + + ss << "\n"; + // TODO: colorspace + ss << "\n"; + ss << pprint::Indent(1) << "\n"; + +#define EMIT_ATTRIBUTE(__name, __tyname, __attr) \ + { \ + std::string value_str; \ + if (!SerializeAttribute(__name, __attr, value_str, err)) { \ + return false; \ + } \ + if (value_str.size()) { \ + ss << pprint::Indent(2) << "\n"; \ + } \ + } + + // TODO: Attribute Connection + EMIT_ATTRIBUTE("diffuseColor", "color3", shader.diffuseColor) + EMIT_ATTRIBUTE("emissiveColor", "color3", shader.emissiveColor) + EMIT_ATTRIBUTE("useSpecularWorkflow", "integer", shader.useSpecularWorkflow) + EMIT_ATTRIBUTE("specularColor", "color3", shader.specularColor) + EMIT_ATTRIBUTE("metallic", "float", shader.metallic) + EMIT_ATTRIBUTE("roughness", "float", shader.roughness) + EMIT_ATTRIBUTE("clearcoat", "float", shader.clearcoat) + EMIT_ATTRIBUTE("clearcoatRoughness", "float", shader.clearcoatRoughness) + EMIT_ATTRIBUTE("opacity", "float", shader.opacity) + EMIT_ATTRIBUTE("opacityThreshold", "float", shader.opacityThreshold) + EMIT_ATTRIBUTE("ior", "float", shader.ior) + EMIT_ATTRIBUTE("normal", "vector3", shader.normal) + EMIT_ATTRIBUTE("displacement", "float", shader.displacement) + EMIT_ATTRIBUTE("occlusion", "float", shader.occlusion) + + ss << pprint::Indent(1) << "\n"; + + ss << pprint::Indent(1) + << "\n"; + ss << pprint::Indent(2) + << "\n"; + ss << pprint::Indent(1) << "\n"; + + ss << "\n"; + + xml_str = ss.str(); + + return true; +} + +static bool ConvertPlace2d(const pugi::xml_node &node, PrimSpec &ps, + std::string *warn, std::string *err) { + // texcoord(vector2). default index=0 uv coordinate + // pivot(vector2). default (0, 0) + // scale(vector2). default (1, 1) + // rotate(float). in degrees, Conter-clockwise + // offset(vector2) + if (pugi::xml_attribute texcoord_attr = node.attribute("texcoord")) { + PUSH_WARN("TODO: `texcoord` attribute.\n"); + } + + if (pugi::xml_attribute pivot_attr = node.attribute("pivot")) { + value::float2 value; + if (!ParseMaterialXValue(pivot_attr.as_string(), &value, err)) { + ps.props()["inputs:pivot"] = Property(Attribute::Uniform(value)); + } + } + + if (pugi::xml_attribute scale_attr = node.attribute("scale")) { + value::float2 value; + if (!ParseMaterialXValue(scale_attr.as_string(), &value, err)) { + PUSH_ERROR_AND_RETURN( + "Failed to parse `rotate` attribute of `place2d`.\n"); + } + ps.props()["inputs:scale"] = Property(Attribute::Uniform(value)); + } + + if (pugi::xml_attribute rotate_attr = node.attribute("rotate")) { + float value; + if (!ParseMaterialXValue(rotate_attr.as_string(), &value, err)) { + PUSH_ERROR_AND_RETURN( + "Failed to parse `rotate` attribute of `place2d`.\n"); + } + ps.props()["inputs:rotate"] = Property(Attribute::Uniform(value)); + } + + pugi::xml_attribute offset_attr = node.attribute("offset"); + if (offset_attr) { + value::float2 value; + if (!ParseMaterialXValue(offset_attr.as_string(), &value, err)) { + PUSH_ERROR_AND_RETURN( + "Failed to parse `offset` attribute of `place2d`.\n"); + } + ps.props()["inputs:offset"] = Property(Attribute::Uniform(value)); + } + + ps.specifier() = Specifier::Def; + ps.typeName() = kShader; + ps.props()[kShaderInfoId] = + Property(Attribute::Uniform(value::token(kUsdTransform2d))); + + return true; +} + +static bool ConvertNodeGraphRec(const uint32_t depth, + const pugi::xml_node &node, PrimSpec &ps_out, + std::string *warn, std::string *err) { + if (depth > (1024 * 1024)) { + PUSH_ERROR_AND_RETURN("Network too deep.\n"); + } + + PrimSpec ps; + + std::string node_name = node.name(); + + if (node_name == "place2d") { + if (!ConvertPlace2d(node, ps, warn, err)) { + return false; + } + } else { + PUSH_ERROR_AND_RETURN("Unknown/unsupported Shader Node: " << node.name()); + } + + for (const auto &child : node.children()) { + PrimSpec child_ps; + if (!ConvertNodeGraphRec(depth + 1, child, child_ps, warn, err)) { + return false; + } + + ps.children().emplace_back(std::move(child_ps)); + } + + ps_out = std::move(ps); + + return true; +} + +#if 0 // TODO +static bool ConvertPlace2d(const pugi::xml_node &node, UsdTransform2d &tx, std::string *warn, std::string *err) { + // texcoord(vector2). default index=0 uv coordinate + // pivot(vector2). default (0, 0) + // scale(vector2). default (1, 1) + // rotate(float). in degrees, Conter-clockwise + // offset(vector2) + if (pugi::xml_attribute texcoord_attr = node.attribute("texcoord")) { + PUSH_WARN("TODO: `texcoord` attribute.\n"); + } + + if (pugi::xml_attribute pivot_attr = node.attribute("pivot")) { + PUSH_WARN("TODO: `pivot` attribute.\n"); + } + + if (pugi::xml_attribute scale_attr = node.attribute("scale")) { + value::float2 value; + if (!ParseMaterialXValue(scale_attr.as_string(), &value, err)) { + PUSH_ERROR_AND_RETURN("Failed to parse `rotate` attribute of `place2d`.\n"); + } + tx.scale = value; + } + + if (pugi::xml_attribute rotate_attr = node.attribute("rotate")) { + float value; + if (!ParseMaterialXValue(rotate_attr.as_string(), &value, err)) { + PUSH_ERROR_AND_RETURN("Failed to parse `rotate` attribute of `place2d`.\n"); + } + tx.rotation = value; + } + + pugi::xml_attribute offset_attr = node.attribute("offset"); + if (offset_attr) { + PUSH_WARN("TODO: `offset` attribute.\n"); + } + + return true; +} + +static bool ConvertTiledImage(const pugi::xml_node &node, UsdUVTexture &tex, std::string *err) { + (void)tex; + // file: uniform filename + // default: float or colorN or vectorN + // texcoord: vector2 + // uvtiling: vector2(default 1.0, 1.0) + // uvoffset: vector2(default 0.0, 0.0) + // realworldimagesize: vector2 + // realworldtilesize: vector2 + // filtertype: string: "closest", "linear" or "cubic" + if (pugi::xml_attribute file_attr = node.attribute("file")) { + std::string filename; + if (!ParseMaterialXValue(file_attr.as_string(), &filename, err)) { + PUSH_ERROR_AND_RETURN("Failed to parse `file` attribute in `tiledimage`.\n"); + } + } else { + PUSH_ERROR_AND_RETURN("`file` attribute not found."); + } + + // TODO... + + return true; + +} +#endif + +} // namespace detail + +bool ReadMaterialXFromString(const std::string &str, + const std::string &asset_path, MtlxModel *mtlx, + std::string *warn, std::string *err) { +#define GET_ATTR_VALUE(__xml, __name, __ty, __var) \ + do { \ + pugi::xml_attribute attr = __xml.attribute(__name); \ + if (!attr) { \ + PUSH_ERROR_AND_RETURN( \ + fmt::format("Required XML Attribute `{}` not found.", __name)); \ + } \ + __ty v; \ + if (!detail::ParseMaterialXValue(attr.as_string(), &v, err)) { \ + return false; \ + } \ + __var = v; \ + } while (0) + +#define GET_SHADER_PARAM(__name, __typeName, __inp_name, __tyname, __ty, \ + __valuestr, __attr) \ + if (__name == __inp_name) { \ + if (__typeName != __tyname) { \ + PUSH_ERROR_AND_RETURN( \ + fmt::format("type `{}` expected for input `{}`, but got `{}`", \ + __typeName, __inp_name, __tyname)); \ + } \ + __ty v; \ + if (!detail::ParseMaterialXValue(__valuestr, &v, err)) { \ + return false; \ + } \ + __attr.set_value(v); \ + } else + + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_string(str.c_str()); + if (!result) { + std::string msg(result.description()); + PUSH_ERROR_AND_RETURN("Failed to parse XML: " + msg); + } + + pugi::xml_node root = doc.child("materialx"); + if (!root) { + PUSH_ERROR_AND_RETURN(" tag not found: " + asset_path); + } + + // Attributes for a element: + // + // - [x] version(string, required) + // - [x] validate version string + // - [x] cms(string, optional) + // - [x] cmsconfig(filename, optional) + // - [x] colorspace(string, optional) + // - [x] namespace(string, optional) + + pugi::xml_attribute ver_attr = root.attribute("version"); + if (!ver_attr) { + PUSH_ERROR_AND_RETURN("version attribute not found in :" + + asset_path); + } + + // parse version string as floating point + { + DCOUT("version = " << ver_attr.as_string()); + float ver{0.0}; + if (!detail::ParseMaterialXValue(ver_attr.as_string(), &ver, err)) { + return false; + } + + if (ver < 1.38f) { + PUSH_ERROR_AND_RETURN( + fmt::format("TinyUSDZ only supports MaterialX version 1.38 or " + "greater, but got {}", + ver_attr.as_string())); + } + mtlx->version = ver_attr.as_string(); + } + + pugi::xml_attribute cms_attr = root.attribute("cms"); + if (cms_attr) { + mtlx->cms = cms_attr.as_string(); + } + + pugi::xml_attribute cmsconfig_attr = root.attribute("cms"); + if (cmsconfig_attr) { + mtlx->cmsconfig = cmsconfig_attr.as_string(); + } + pugi::xml_attribute colorspace_attr = root.attribute("colorspace"); + if (colorspace_attr) { + mtlx->color_space = colorspace_attr.as_string(); + } + + pugi::xml_attribute namespace_attr = root.attribute("namespace"); + if (namespace_attr) { + mtlx->name_space = namespace_attr.as_string(); + } + + std::vector nodegraph_pss; + + // NodeGraph + for (auto ng : root.children("nodegraph")) { + PrimSpec root_ps; + if (detail::ConvertNodeGraphRec(0, ng, root_ps, warn, err)) { + return false; + } + + nodegraph_pss.emplace_back(std::move(root_ps)); + } + + // standard_surface + for (auto sd_surface : root.children("standard_surface")) { + PUSH_WARN("TODO: `look`"); + // TODO + (void)sd_surface; + } + + // standard_surface + for (auto usd_surface : root.children("UsdPreviewSurface")) { + std::string surface_name; + { + std::string typeName; + + GET_ATTR_VALUE(usd_surface, "name", std::string, surface_name); + GET_ATTR_VALUE(usd_surface, "type", std::string, typeName); + + if (typeName != "surfaceshader") { + PUSH_ERROR_AND_RETURN( + fmt::format("`surfaceshader` expected for type of " + "UsdPreviewSurface, but got `{}`", + typeName)); + } + } + + MtlxUsdPreviewSurface surface; + for (auto inp : usd_surface.children("input")) { + std::string name; + std::string typeName; + std::string valueStr; + GET_ATTR_VALUE(inp, "name", std::string, name); + GET_ATTR_VALUE(inp, "type", std::string, typeName); + GET_ATTR_VALUE(inp, "value", std::string, valueStr); + + // TODO: connection + GET_SHADER_PARAM(name, typeName, "diffuseColor", "color3", value::color3f, + valueStr, surface.diffuseColor) + GET_SHADER_PARAM(name, typeName, "emissiveColor", "color3", + value::color3f, valueStr, surface.emissiveColor) + GET_SHADER_PARAM(name, typeName, "useSpecularWorkflow", "integer", int, + valueStr, surface.useSpecularWorkflow) + GET_SHADER_PARAM(name, typeName, "specularColor", "color3", + value::color3f, valueStr, surface.specularColor) + GET_SHADER_PARAM(name, typeName, "metallic", "float", float, valueStr, + surface.metallic) + GET_SHADER_PARAM(name, typeName, "roughness", "float", float, valueStr, + surface.roughness) + GET_SHADER_PARAM(name, typeName, "clearcoat", "float", float, valueStr, + surface.clearcoat) + GET_SHADER_PARAM(name, typeName, "clearcoatRoughness", "float", float, + valueStr, surface.clearcoatRoughness) + GET_SHADER_PARAM(name, typeName, "opacity", "float", float, valueStr, + surface.opacity) + GET_SHADER_PARAM(name, typeName, "opacityThreshold", "float", float, + valueStr, surface.opacityThreshold) + GET_SHADER_PARAM(name, typeName, "ior", "float", float, valueStr, + surface.ior) + GET_SHADER_PARAM(name, typeName, "normal", "vector3f", value::normal3f, + valueStr, surface.normal) + GET_SHADER_PARAM(name, typeName, "displacement", "float", float, valueStr, + surface.displacement) + GET_SHADER_PARAM(name, typeName, "occlusion", "float", float, valueStr, + surface.occlusion) { + PUSH_WARN("Unknown/unsupported input " << name); + } + } + + mtlx->shaders[surface_name] = surface; + } + + // surfacematerial + for (auto surfacematerial : root.children("surfacematerial")) { + std::string material_name; + { + std::string typeName; + GET_ATTR_VALUE(surfacematerial, "name", std::string, material_name); + GET_ATTR_VALUE(surfacematerial, "type", std::string, typeName); + + if (typeName != "material") { + PUSH_ERROR_AND_RETURN(fmt::format( + "`material` expected for type of surfacematerial, but got `{}`", + typeName)); + } + } + + std::string typeName; + std::string nodename; + for (auto inp : surfacematerial.children("input")) { + std::string name; + GET_ATTR_VALUE(inp, "name", std::string, name); + GET_ATTR_VALUE(inp, "type", std::string, typeName); + GET_ATTR_VALUE(inp, "nodename", std::string, nodename); + + if (name != "surfaceshader") { + PUSH_ERROR_AND_RETURN( + fmt::format("Currently only `surfaceshader` supported for " + "`surfacematerial`'s input, but got `{}`", + name)); + } + + if (typeName != "surfaceshader") { + PUSH_ERROR_AND_RETURN( + fmt::format("Currently only `surfaceshader` supported for " + "`surfacematerial` input type, but got `{}`", + typeName)); + } + } + + MtlxMaterial mat; + mat.name = material_name; + mat.typeName = typeName; + mat.nodename = nodename; + mtlx->surface_materials[material_name] = mat; + } + + // look. + for (auto look : root.children("look")) { + PUSH_WARN("TODO: `look`"); + // TODO + (void)look; + } + +#undef GET_ATTR_VALUE + + return true; +} + +bool ReadMaterialXFromFile(const AssetResolutionResolver &resolver, + const std::string &asset_path, MtlxModel *mtlx, + std::string *warn, std::string *err) { + std::string filepath = resolver.resolve(asset_path); + if (filepath.empty()) { + PUSH_ERROR_AND_RETURN("Asset not found: " + asset_path); + } + + // up to 16MB xml + size_t max_bytes = 1024 * 1024 * 16; + + std::vector data; + if (!io::ReadWholeFile(&data, err, filepath, max_bytes, + /* userdata */ nullptr)) { + PUSH_ERROR_AND_RETURN("Read file failed."); + } + + std::string str(reinterpret_cast(&data[0]), data.size()); + return ReadMaterialXFromString(str, asset_path, mtlx, warn, err); +} + +bool WriteMaterialXToString(const MtlxModel &mtlx, std::string &xml_str, + std::string *warn, std::string *err) { + if (auto usdps = mtlx.shader.as()) { + return detail::WriteMaterialXToString(*usdps, xml_str, warn, err); + } else if (auto adskss = mtlx.shader.as()) { + // TODO + PUSH_ERROR_AND_RETURN("TODO: AutodeskStandardSurface"); + } else { + // TODO + PUSH_ERROR_AND_RETURN("Unknown/unsupported shader: " << mtlx.shader_name); + } + + return false; +} + +bool ToPrimSpec(const MtlxModel &model, PrimSpec &ps, std::string *err) { + // + // def "MaterialX" { + // + // def "Materials" { + // def Material ... { + // } + // } + // def "Shaders" { + // } + constexpr auto kAutodeskStandardSurface = "AutodeskStandardSurface"; + + if (model.shader_name == kUsdPreviewSurface) { + ps.props()["info:id"] = + detail::MakeProperty(value::token(kUsdPreviewSurface)); + } else if (model.shader_name == kAutodeskStandardSurface) { + ps.props()["info:id"] = + detail::MakeProperty(value::token(kAutodeskStandardSurface)); + } else { + PUSH_ERROR_AND_RETURN("Unsupported shader_name: " << model.shader_name); + } + + PrimSpec materials; + materials.name() = "Materials"; + materials.specifier() = Specifier::Def; + + for (const auto &item : model.surface_materials) { + PrimSpec material; + material.specifier() = Specifier::Def; + material.typeName() = "Material"; + + material.name() = item.second.name; + } + + PrimSpec shaders; + shaders.name() = "Shaders"; + shaders.specifier() = Specifier::Def; + + PrimSpec root; + root.name() = "MaterialX"; + root.specifier() = Specifier::Def; + + root.children().push_back(materials); + root.children().push_back(shaders); + + ps = std::move(root); + + return true; +} + +bool LoadMaterialXFromAsset(const Asset &asset, const std::string &asset_path, + PrimSpec &ps /* inout */, std::string *warn, + std::string *err) { + (void)asset_path; + (void)warn; + + if (asset.size() < 32) { + if (err) { + (*err) += "MateiralX: Asset size too small.\n"; + } + return false; + } + + std::string str(reinterpret_cast(asset.data()), asset.size()); + + MtlxModel mtlx; + if (!ReadMaterialXFromString(str, asset_path, &mtlx, warn, err)) { + PUSH_ERROR_AND_RETURN("Failed to read MaterialX."); + } + + if (!ToPrimSpec(mtlx, ps, err)) { + PUSH_ERROR_AND_RETURN("Failed to convert MaterialX to USD PrimSpec."); + } + + return true; +} + +//} // namespace usdMtlx +} // namespace tinyusdz + +#else + +namespace tinyusdz { + +bool ReadMaterialXFromFile(const AssetResolutionResolver &resolver, + const std::string &asset_path, MtlxModel *mtlx, + std::string *warn, std::string *err) { + (void)resolver; + (void)asset_path; + (void)mtlx; + (void)warn; + + if (err) { + (*err) += "MaterialX support is disabled in this build.\n"; + } + return false; +} + +bool WriteMaterialXToString(const MtlxModel &mtlx, std::string &xml_str, + std::string *warn, std::string *err) { + (void)mtlx; + (void)xml_str; + (void)warn; + + if (err) { + (*err) += "MaterialX support is disabled in this build.\n"; + } + return false; +} + +bool LoadMaterialXFromAsset(const Asset &asset, const std::string &asset_path, + PrimSpec &ps /* inout */, std::string *warn, + std::string *err) { + (void)asset; + (void)asset_path; + (void)ps; + (void)warn; + + if (err) { + (*err) += "MaterialX support is disabled in this build.\n"; + } + + return false; +} + +#if 0 +bool ToPrimSpec(const MtlxModel &model, PrimSpec &ps, std::string *err) + (void)model; + (void)ps; + + if (err) { + (*err) += "MaterialX support is disabled in this build.\n"; + } + return false; + +} +#endif + +} // namespace tinyusdz + +#endif // TINYUSDZ_USE_USDMTLX diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usdMtlx.hh b/contrib/tinyusdz/tinyusdz_repo/src/usdMtlx.hh new file mode 100644 index 000000000..73c7c76ce --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usdMtlx.hh @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2023 - Present, Light Transport Entertainment, Inc. +// +// Predefined MaterialX shadingmodel & Built-in MaterialX XML import plugIn. +// Import only. Export is not supported(yet). +// +// example usage +// +// def Shader "mesh" ( +// prepend references = @myshader.mtlx@ +// ) +// { +// ... +// } +// +// Based on MaterialX spec v1.38 + +#pragma once + +#include + +#include "asset-resolution.hh" +#include "usdShade.hh" + + +namespace tinyusdz { + +constexpr auto kMtlxUsdPreviewSurface = "MtlxUsdPreviewSurface"; +constexpr auto kMtlxAutodeskStandardSurface = "MtlxAutodeskStandaradSurface"; + + +namespace mtlx { + +enum class ColorSpace { + Lin_rec709, // lin_rec709 + Unknown +}; + +} // namespace mtlx + +// +struct MtlxMaterial { + std::string name; + std::string typeName; + std::string nodename; +}; + +struct MtlxModel { + std::string asset_name; + + std::string version; + std::string cms; + std::string cmsconfig; // filename + std::string color_space; // colorspace + std::string name_space; // namespace + + //mtlx::ColorSpace colorspace{Lin_rec709}; + // TODO + + std::string shader_name; + + // Content of shader. + // MtlxUsdPreviewSurface or MtlxAutodeskStandaradSurface + value::Value shader; + + std::map surface_materials; + std::map shaders; // MtlxUsdPreviewSurface or MtlxAutodeskStandaradSurface +}; + +struct MtlxUsdPreviewSurface : UsdPreviewSurface { + // TODO: add mtlx specific attribute. +}; + +// https://github.com/Autodesk/standard-surface/blob/master/reference/standard_surface.mtlx +// We only support v1.0.1 +struct MtlxAutodeskStandardSurface : ShaderNode { + TypedAttributeWithFallback> base{1.0f}; + TypedAttributeWithFallback> baseColor{ + value::color3f{0.8f, 0.8f, 0.8f}}; // color3 + + // TODO + // ... + + // (coat_affect_roughness * coat) * coat_roughness + TypedAttribute> coat_affect_roughness; + TypedAttribute> coat; + TypedAttribute> coat_roughness; + + // (specular_roughness + transmission_extra_roughness) + TypedAttribute> specular_roughness; + TypedAttribute> transmission_extra_roughness; + TypedAttribute> transmission_roughness_add; + + // tangent_rotate_normalize + // normalize(rotate3d(/* in */tangent, /*amount*/(specular_rotation * 360), /* + // axis */normal)) + TypedAttribute> specular_rotation; + + // Output + TypedTerminalAttribute out; // 'out' +}; + +// +// IO +// + +/// +/// Load MaterialX XML from a string. +/// +/// @param[in] str String representation of XML data. +/// @param[in] asset_name Corresponding asset name. Can be empty. +/// @param[out] mtlx Output +/// @param[out] warn Warning message +/// @param[out] err Error message +/// +/// @return true upon success. +bool ReadMaterialXFromString(const std::string &str, const std::string &asset_name, MtlxModel *mtlx, + std::string *warn, std::string *err); + +/// +/// Load MaterialX XML from a file. +/// +/// @param[in] str String representation of XML data. +/// @param[in] asset_name Corresponding asset name. Can be empty. +/// @param[out] mtlx Output +/// @param[out] err Error message +/// +/// @return true upon success. +/// +/// TODO: Use FileSystem handler + +bool ReadMaterialXFromFile(const AssetResolutionResolver &resolver, + const std::string &asset_path, MtlxModel *mtlx, + std::string *warn, std::string *err); + +bool WriteMaterialXToString(const MtlxModel &mtlx, std::string &xml_str, + std::string *warn, std::string *err); + +bool ToPrimSpec(const MtlxModel &model, PrimSpec &ps, std::string *err); + +/// +/// Load MaterialX from Asset and construct USD PrimSpec +/// +bool LoadMaterialXFromAsset(const Asset &asset, + const std::string &asset_path, PrimSpec &ps /* inout */, + std::string *warn, std::string *err); + +// import DEFINE_TYPE_TRAIT and DEFINE_ROLE_TYPE_TRAIT +#include "define-type-trait.inc" + +namespace value { + +// ShaderNodes +DEFINE_TYPE_TRAIT(MtlxUsdPreviewSurface, kMtlxUsdPreviewSurface, + TYPE_ID_IMAGING_MTLX_PREVIEWSURFACE, 1); +DEFINE_TYPE_TRAIT(MtlxAutodeskStandardSurface, kMtlxAutodeskStandardSurface, + TYPE_ID_IMAGING_MTLX_STANDARDSURFACE, 1); + +#undef DEFINE_TYPE_TRAIT +#undef DEFINE_ROLE_TYPE_TRAIT + +} // namespace value + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usdObj.cc b/contrib/tinyusdz/tinyusdz_repo/src/usdObj.cc new file mode 100644 index 000000000..90694a94d --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usdObj.cc @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: MIT +// +// Built-in .obj import plugIn. +// Import only. Writing scene data as .obj is not supported. +// +// example usage +// +// def "mesh" ( +// prepend references = @bunny.obj@ +// ) +// { +// ... +// } + +#include + +#include "tinyusdz.hh" +#include "io-util.hh" +#include "usdObj.hh" + +//#include "math-util.inc" + +#ifdef TINYUSDZ_USE_USDOBJ + +// Assume implementation is done in external/tiny_obj_loader.cc +//#define TINYOBJLOADER_IMPLEMENTATION +#include "external/tiny_obj_loader.h" + +#endif + +namespace tinyusdz { + +namespace usdObj { + +namespace { + + + +} + +bool ReadObjFromFile(const std::string &filepath, tinyusdz::GPrim *prim, std::string *err) +{ +#if !defined(TINYUSDZ_USE_USDOBJ) + (void)filepath; + (void)prim; + if (err) { + (*err) = "usdObj is disabled in this build.\n"; + } + return false; +#else + + std::vector buf; + if (!io::ReadWholeFile(&buf, err, filepath, /* filesize max */ 0, + /* user_ptr */ nullptr)) { + return false; + } + + std::string str(reinterpret_cast(buf.data()), buf.size()); + + return ReadObjFromString(str, prim, err); + +#endif + +} + + +bool ReadObjFromString(const std::string &str, tinyusdz::GPrim *prim, std::string *err) +{ +#if !defined(TINYUSDZ_USE_USDOBJ) + (void)str; + (void)prim; + if (err) { + (*err) = "usdObj is disabled in this build.\n"; + } + return false; +#else + + tinyobj::ObjReaderConfig config; + // do not triangulate + config.triangulate = false; + + tinyobj::ObjReader reader; + + // ignore material + const std::string mtl_text; + + bool ret = reader.ParseFromString(str, mtl_text, config); + + if (!ret) { + if (err) { + (*err) += reader.Warning(); + (*err) += reader.Error(); + } + + return false; + } + + const auto &attrs = reader.GetAttrib(); + + Attribute pointsAttr; + if ((attrs.vertices.size() % 3) != 0) { + if (err) { + (*err) += "[usdObj] Invalid vertices data.\n"; + } + return false; + } + + // std::vector -> std::vector + std::vector pts(attrs.vertices.size() / 3); + memcpy(pts.data(), attrs.vertices.data(), sizeof(float) * 3 * pts.size()); + primvar::PrimVar ptsVar; + ptsVar.set_value(pts); + pointsAttr.set_var(std::move(ptsVar)); + Property pointsProp(pointsAttr, /* custom */false); + prim->props.emplace("points", pointsProp); + + const auto &shapes = reader.GetShapes(); + + // Combine all shapes into single mesh. + std::vector vertexIndices; + std::vector vertexCounts; + + // Make normals and texcoords facevarying + // TODO: provide indices for each normals and uvs + std::vector facevaryingTexcoords; + std::vector facevaryingNormals; + + { + size_t index_offset = 0; + + for (size_t i = 0; i < shapes.size(); i++) { + const tinyobj::shape_t &shape = shapes[i]; + + for (size_t f = 0; f < shape.mesh.num_face_vertices.size(); f++) { + size_t num_v = shape.mesh.num_face_vertices[f]; + + vertexCounts.push_back(int32_t(num_v)); + + if (num_v < 3) { + if (err) { + (*err) = "Degenerated face found."; + } + return false; + } + + size_t num_fvn = 0; + for (size_t v = 0; v < num_v; v++) { + tinyobj::index_t idx = shape.mesh.indices[index_offset + v]; + vertexIndices.push_back(int32_t(idx.vertex_index)); + + if (idx.normal_index > -1) { + value::float3 normal; + normal[0] = attrs.normals[3 * size_t(idx.normal_index) + 0]; + normal[1] = attrs.normals[3 * size_t(idx.normal_index) + 1]; + normal[2] = attrs.normals[3 * size_t(idx.normal_index) + 2]; + facevaryingNormals.push_back(normal); + num_fvn++; + } else { + facevaryingNormals.push_back({0.0f, 0.0f, 0.0f}); + } + + if (idx.texcoord_index > -1) { + value::float2 texcoord; + texcoord[0] = attrs.texcoords[2 * size_t(idx.texcoord_index) + 0]; + texcoord[1] = attrs.texcoords[2 * size_t(idx.texcoord_index) + 1]; + } else { + facevaryingTexcoords.push_back({0.0f, 0.0f}); + } + } + + if (num_fvn == 0) { + // No per-vertex normal. + // Compute geometric normal from p0, p1, p(N-1) + // This won't give correct geometric normal for n-gons(n >= 4) + value::float3 p0, p1, p2; + + uint32_t vidx0 = uint32_t(shape.mesh.indices[index_offset + 0].vertex_index); + uint32_t vidx1 = uint32_t(shape.mesh.indices[index_offset + 1].vertex_index); + uint32_t vidx2 = uint32_t(shape.mesh.indices[index_offset + (num_v - 1)].vertex_index); + + p0[0] = attrs.vertices[3 * vidx0 + 0]; + p0[1] = attrs.vertices[3 * vidx0 + 1]; + p0[2] = attrs.vertices[3 * vidx0 + 2]; + + p1[0] = attrs.vertices[3 * vidx1 + 0]; + p1[1] = attrs.vertices[3 * vidx1 + 1]; + p1[2] = attrs.vertices[3 * vidx1 + 2]; + + p2[0] = attrs.vertices[3 * vidx2 + 0]; + p2[1] = attrs.vertices[3 * vidx2 + 1]; + p2[2] = attrs.vertices[3 * vidx2 + 2]; + + value::float3 n = geometric_normal(p0, p1, p2); + + for (size_t v = 0; v < num_v; v++) { + facevaryingNormals.push_back(n); + } + } + + // TODO: normal, texcoords + + index_offset += num_v; + } + + + } + + // TODO: per-face material? + } + + { + primvar::PrimVar var; + var.set_value(vertexIndices); + Attribute attr; + attr.set_var(std::move(var)); + Property prop(attr, false); + prim->props.emplace("faceVertexIndices", prop); + } + + { + primvar::PrimVar var; + var.set_value(vertexCounts); + Attribute attr; + attr.set_var(std::move(var)); + Property prop(attr, false); + prim->props.emplace("faceVertexCounts", prop); + } + + { + primvar::PrimVar var; + var.set_value(facevaryingNormals); + + Attribute normalsAttr; + normalsAttr.metas().interpolation = Interpolation::FaceVarying; + normalsAttr.variability() = Variability::Varying; // FIXME + normalsAttr.set_var(std::move(var)); + Property prop(normalsAttr, false); + + // Use primvars::normals? + prim->props.emplace("primvars::normals", prop); + } + + { + primvar::PrimVar var; + var.set_value(facevaryingTexcoords); + + Attribute texcoordsAttr; + texcoordsAttr.metas().interpolation = Interpolation::FaceVarying; + texcoordsAttr.variability() = Variability::Varying; + texcoordsAttr.set_var(std::move(var)); + Property prop(texcoordsAttr, false); + + + prim->props.emplace("prmvars:uv", prop); + } + + // TODO: read skin weight/indices + + return true; +#endif +} + +} // namespace usdObj + +} // tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usdObj.hh b/contrib/tinyusdz/tinyusdz_repo/src/usdObj.hh new file mode 100644 index 000000000..f814c2255 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usdObj.hh @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +// +// Built-in .obj import plugIn. +// Import only. Writing scene data as .obj is not supported. +// +// example usage +// +// def "mesh" ( +// prepend references = @bunny.obj@ +// ) +// { +// ... +// } + +#pragma once + +#include "tinyusdz.hh" + +namespace tinyusdz { + +namespace usdObj { + +bool ReadObjFromString(const std::string &str, GPrim *prim, std::string *err = nullptr); +bool ReadObjFromFile(const std::string &filepath, GPrim *prim, std::string *err = nullptr); + +} // namespace usdObj + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usdShade.cc b/contrib/tinyusdz/tinyusdz_repo/src/usdShade.cc new file mode 100644 index 000000000..a2db026b9 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usdShade.cc @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +// UsdGeom API implementations + +#include + +#include "usdShade.hh" +#include "str-util.hh" + +#include "common-macros.inc" + +namespace tinyusdz { + +std::string to_string(const MaterialBindingStrength strength) { + switch (strength) { + case MaterialBindingStrength::WeakerThanDescendants: { + return kWeaderThanDescendants; + } + case MaterialBindingStrength::StrongerThanDescendants: { + return kStrongerThanDescendants; + } + } + + return "[[Invalid MaterialBindingStrength]]"; +} + +bool UsdShadePrim::has_sdr_metadata(const std::string &key) { + if (!metas().sdrMetadata.has_value()) { + return false; + } + + const Dictionary &dict = metas().sdrMetadata.value(); + + if (!HasCustomDataKey(dict, key)) { + return false; + } + + // check the type of value. + MetaVariable value; + if (!GetCustomDataByKey(dict, key, &value)) { + return false; + } + + if (value.type_id() != value::TypeTraits::type_id()) { + return false; + } + + return true; +} + +const std::string UsdShadePrim::get_sdr_metadata(const std::string &key) { + if (!metas().sdrMetadata.has_value()) { + return std::string(); + } + + const Dictionary &dict = metas().sdrMetadata.value(); + + if (!HasCustomDataKey(dict, key)) { + return std::string(); + } + + // check the type of value. + MetaVariable var; + if (!GetCustomDataByKey(dict, key, &var)) { + return std::string(); + } + + if (var.type_id() != value::TypeTraits::type_id()) { + return std::string(); + } + + std::string svalue; + if (!var.get_value(&svalue)) { + return std::string(); + } + + return svalue; +} + +bool UsdShadePrim::set_sdr_metadata(const std::string &key, const std::string &value) { + + Dictionary &dict = metas().sdrMetadata.value(); + bool ret = SetCustomDataByKey(key, value, dict); + return ret; +} + +value::token MaterialBinding::get_materialBindingStrength(const value::token &purpose) { + + if (purpose.str() == kAllPurpose().str()) { + if (materialBinding && materialBinding.value().metas().bindMaterialAs) { + return materialBinding.value().metas().bindMaterialAs.value(); + } + } else if (purpose.str() == "full") { + if (materialBindingFull && materialBindingFull.value().metas().bindMaterialAs) { + return materialBindingFull.value().metas().bindMaterialAs.value(); + } + } else if (purpose.str() == "preview") { + if (materialBindingPreview && materialBindingPreview.value().metas().bindMaterialAs) { + return materialBindingPreview.value().metas().bindMaterialAs.value(); + } + } else { + if (_materialBindingMap.count(purpose.str())) { + const auto &m = _materialBindingMap.at(purpose.str()); + if (m.metas().bindMaterialAs) { + return m.metas().bindMaterialAs.value(); + } + } + } + + return value::token(kWeaderThanDescendants); +} + +value::token MaterialBinding::get_materialBindingStrengthCollection(const value::token &coll_name, const value::token &purpose) { + + if (coll_name.str().empty()) { + return get_materialBindingStrength(purpose); + } + + if (_materialBindingCollectionMap.count(coll_name.str())) { + const auto &coll_mb = _materialBindingCollectionMap.at(coll_name.str()); + + if (coll_mb.count(purpose.str())) { + const Relationship *prel{nullptr}; + if (coll_mb.at(purpose.str(), &prel)) { + if (prel->metas().bindMaterialAs) { + return prel->metas().bindMaterialAs.value(); + } + } + } + } + + return value::token(kWeaderThanDescendants); +} + +namespace { + +//constexpr auto kPrimvarPrefix = "primvar::"; + +} // namespace + + +} // namespace tinyusdz + + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usdShade.hh b/contrib/tinyusdz/tinyusdz_repo/src/usdShade.hh new file mode 100644 index 000000000..920f76ae1 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usdShade.hh @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +// Material and Shader. And more, TinyUSDZ implmenents some usdImaging stuff here. +// +// TODO: +// - [ ] Consider `interfaceOnly` connection +// - [ ] Strict usdShade interpretation https://graphics.pixar.com/usd/release/api/usd_shade_page_front.html +// - [ ] MaterialX support(in usdMtlx.hh) +// - [ ] NodeGraph support +// +#pragma once + +#include "prim-types.hh" + +namespace tinyusdz { + +constexpr auto kMaterial = "Material"; +constexpr auto kShader = "Shader"; +constexpr auto kNodeGraph = "NodeGraph"; +constexpr auto kShaderNode = "ShaderNode"; + +constexpr auto kShaderInfoId = "info:id"; + +constexpr auto kUsdPreviewSurface = "UsdPreviewSurface"; +constexpr auto kUsdUVTexture = "UsdUVTexture"; +constexpr auto kUsdTransform2d = "UsdTransform2d"; +constexpr auto kUsdPrimvarReader_int = "UsdPrimvarReader_int"; +constexpr auto kUsdPrimvarReader_float = "UsdPrimvarReader_float"; +constexpr auto kUsdPrimvarReader_float2 = "UsdPrimvarReader_float2"; +constexpr auto kUsdPrimvarReader_float3 = "UsdPrimvarReader_float3"; +constexpr auto kUsdPrimvarReader_float4 = "UsdPrimvarReader_float4"; +constexpr auto kUsdPrimvarReader_string = "UsdPrimvarReader_string"; +constexpr auto kUsdPrimvarReader_normal = "UsdPrimvarReader_normal"; +constexpr auto kUsdPrimvarReader_point = "UsdPrimvarReader_point"; +constexpr auto kUsdPrimvarReader_vector = "UsdPrimvarReader_vector"; +constexpr auto kUsdPrimvarReader_matrix = "UsdPrimvarReader_matrix"; + +// TODO: Inherit from Prim? +struct UsdShadePrim { + std::string name; + Specifier spec{Specifier::Def}; + + int64_t parent_id{-1}; + + PrimMeta meta; // TODO: move to private + + const PrimMeta &metas() const { return meta; } + PrimMeta &metas() { return meta; } + + // Check if `key` exists in `sdrMetadata` metadatum. + // Return false when `key` is not found in `sdrMetadata`, or corrensponding item is not a string type. + bool has_sdr_metadata(const std::string &key); + + // Get value from `sdrMetadata` metadatum. + // Return empty string when `key` is not found in `sdrMetadata`, or corrensponding item is not a string type. + const std::string get_sdr_metadata(const std::string &key); + + // Set value to `sdrMetadata` metadatum. + // Return false when error(e.g. `key` contains invalid character for USD dictionary) + bool set_sdr_metadata(const std::string &key, const std::string &value); + + TypedAttributeWithFallback purpose{ + Purpose::Default}; // "uniform token purpose" + + std::pair> references; + std::pair> payload; + std::map variantSet; + // Custom properties + std::map props; + + const std::vector &primChildrenNames() const { return _primChildren; } + const std::vector &propertyNames() const { return _properties; } + std::vector &primChildrenNames() { return _primChildren; } + std::vector &propertyNames() { return _properties; } + + private: + std::vector _primChildren; + std::vector _properties; +}; + +// +// Similar to Maya's ShadingGroup +// +struct Material : UsdShadePrim { + + /// + /// NOTE: Mateiral's outputs must be a connection. + /// (Whereas Shader's outputs is not) + /// + TypedConnection surface; // "token outputs:surface.connect" + TypedConnection displacement; // "token outputs:displacement.connect" + TypedConnection volume; // "token outputs:volume.connect" + + +}; + +// TODO +struct NodeGraph : UsdShadePrim { + +}; + +// +// Base class of ShaderNode. Maybe similar to SdrShaderNode in pxrUSD +// +struct ShaderNode : UsdShadePrim { + +}; + +template +struct UsdPrimvarReader : ShaderNode { + + TypedAttribute> fallback; // "inputs:fallback" + + TypedAttribute> varname; // "string inputs:varname". Name of the primvar to be fetched from the geometry("primvar" namespace is omitted). NOTE: older spec uses `token` type: https://openusd.org/release/spec_usdpreviewsurface.html#version-2-3 + + + /// + /// Outputs + /// + TypedTerminalAttribute result; // Terminal attr. "T outputs:result" + +}; + +using UsdPrimvarReader_float = UsdPrimvarReader; +using UsdPrimvarReader_float2 = UsdPrimvarReader; +using UsdPrimvarReader_float3 = UsdPrimvarReader; +using UsdPrimvarReader_float4 = UsdPrimvarReader; +using UsdPrimvarReader_int = UsdPrimvarReader; +using UsdPrimvarReader_string = UsdPrimvarReader; + +// The underlying type is float precision for `normal`, `vector` and `point` +using UsdPrimvarReader_normal = UsdPrimvarReader; +using UsdPrimvarReader_vector = UsdPrimvarReader; +using UsdPrimvarReader_point = UsdPrimvarReader; + +// The underlying type is matrix4d +using UsdPrimvarReader_matrix = UsdPrimvarReader; + +// TODO: Remove +//using UsdPrimvarReaderType = +// tinyusdz::variant; + + +struct UsdUVTexture : ShaderNode { + + // NOTE: transparent black(0, 0, 0, 0) for "black" + // https://github.com/PixarAnimationStudios/OpenUSD/commit/2cf6612b2b1d5a1a1031bc153867116c5963e605 + enum class Wrap { + UseMetadata, // "useMetadata" (default) + Black, // "black" + Clamp, // "clamp" + Repeat, // "repeat" + Mirror, // "mirror" + }; + + enum class SourceColorSpace { + Auto, // "auto"(default) + Raw, // "raw" + SRGB, // "sRGB + }; + + TypedAttribute> file; // "asset inputs:file" interfaceOnly + + TypedAttributeWithFallback> st{value::texcoord2f{0.0f, 0.0f}}; // "inputs:st" + + TypedAttributeWithFallback> wrapS{Wrap::UseMetadata}; // "token inputs:wrapS" interfaceOnly + TypedAttributeWithFallback> wrapT{Wrap::UseMetadata}; // "token inputs:wrapT" interfaceOnly + + TypedAttributeWithFallback fallback{{0.0f, 0.0f, 0.0f, 1.0f}}; // "inputs:fallback" Fallback value when no texture is connected(TODO: Disallow Relation?(i.e, `fallback.connect = `) + + TypedAttributeWithFallback> sourceColorSpace{SourceColorSpace::Auto}; // "token inputs:sourceColorSpace" interfaceOnly + + TypedAttributeWithFallback scale{{1.0f, 1.0f, 1.0f, 1.0f}}; // "inputs:scale" interfaceOnly + TypedAttributeWithFallback bias{{0.0f, 0.0f, 0.0f, 0.0f}}; // "inputs:bias" interfaceOnly + + /// + /// Outputs + /// + /// Terminal attribute. + /// + TypedTerminalAttribute outputsR; // "float outputs:r" + TypedTerminalAttribute outputsG; // "float outputs:g" + TypedTerminalAttribute outputsB; // "float outputs:b" + TypedTerminalAttribute outputsA; // "float outputs:a" + TypedTerminalAttribute outputsRGB; // "float outputs:rgb" in schema. Allow color3f as well(please use TypedTerminalAttribute::get_actual_type_name() to get a actual type name in USDA/USDC). + + // TODO: orientation? + // https://graphics.pixar.com/usd/docs/UsdPreviewSurface-Proposal.html#UsdPreviewSurfaceProposal-TextureCoordinateOrientationinUSD +}; + +// UsdPreviewSurface +// USD's default? PBR shader +// https://graphics.pixar.com/usd/docs/UsdPreviewSurface-Proposal.html +// $USD/pxr/usdImaging/plugin/usdShaders/shaders/shaderDefs.usda + +struct UsdPreviewSurface : ShaderNode { + + TypedAttributeWithFallback> diffuseColor{value::color3f{0.18f, 0.18f, 0.18f}}; // "inputs:diffuseColor" + TypedAttributeWithFallback> emissiveColor{value::color3f{0.0f, 0.0f, 0.0f}}; // "inputs:emissiveColor" + + TypedAttributeWithFallback> useSpecularWorkflow{0}; // "inputs:useSpecularWorkflow" + + // specular workflow + TypedAttributeWithFallback> specularColor{value::color3f{0.0f, 0.0f, 0.0f}}; + + // metalness workflow + //TypedAttributeWithFallback metallic{0.0f}; // "inputs:metallic" + TypedAttributeWithFallback> metallic{0.0f}; // "inputs:metallic" + + // + TypedAttributeWithFallback> clearcoat{0.0f}; // "inputs:clearcoat" + TypedAttributeWithFallback> clearcoatRoughness{0.01f}; // "inputs:clearcoatRouighness" + TypedAttributeWithFallback> roughness{0.5f}; // "inputs:roughness" + TypedAttributeWithFallback> opacity{1.0f}; // "inputs:opacity" + TypedAttributeWithFallback> opacityThreshold{0.0f}; // "inputs:opacityThreshold" + TypedAttributeWithFallback> ior{1.5f}; // "inputs:ior" + + TypedAttributeWithFallback> normal{value::normal3f{0.0f, 0.0f, 1.0f}}; // "inputs:normal" + TypedAttributeWithFallback> displacement{0.0f}; // "inputs:displacement" + TypedAttributeWithFallback> occlusion{0.0f}; // "inputs:occlusion" + + /// + /// Outputs + /// + /// No value assigned. + /// + TypedTerminalAttribute outputsSurface; // "token outputs:surface" + TypedTerminalAttribute outputsDisplacement; // "token outputs:displacement" + +}; + +// Transform texture coordinates. +struct UsdTransform2d : ShaderNode { + + TypedAttributeWithFallback> in{value::float2{0.0f, 0.0f}}; // "inputs:in" Usually connected to UsdPrimvarReader_float2 + + // Transform is TRS order: + // + // result = in * scale * rotate * translation (in USD's notation(row-major, pre-multiply matrix mul)) + // result = translation * rotate * scale * in (in OpenGL's notation(column-major, post-multiply matrix mul)) + + TypedAttributeWithFallback> rotation{0.0f}; // "inputs:rotation" CCW, in degree. + TypedAttributeWithFallback> scale{value::float2{1.0f, 1.0f}}; // "inputs:scale" + TypedAttributeWithFallback> translation{value::float2{0.0f, 0.0f}}; // "inputs:translation" + + + /// + /// Outputs + /// + TypedTerminalAttribute result; // "float2 outputs:result" + +}; + +// Shader Prim +struct Shader : UsdShadePrim { + + std::string info_id; // ShaderNode type. + + // ShaderNode, UsdPreviewSurface, UsdUVTexture, UsdPrimvarReader_float2, ... + // TODO: Use ShaderNode *? + value::Value value; +#if 0 + // Currently we only support PreviewSurface, UVTexture and + // PrimvarReader_float2 + tinyusdz::variant + value; +#endif + +}; + +// import DEFINE_TYPE_TRAIT and DEFINE_ROLE_TYPE_TRAIT +#include "define-type-trait.inc" + +namespace value { + +// Mateiral Prim +DEFINE_TYPE_TRAIT(Material, kMaterial, + TYPE_ID_MATERIAL, 1); + +// Shader Prim +DEFINE_TYPE_TRAIT(Shader, kShader, + TYPE_ID_SHADER, 1); + +// NodeGraph Prim +DEFINE_TYPE_TRAIT(NodeGraph, kNodeGraph, + TYPE_ID_NODEGRAPH, 1); + + +// ShaderNodes +DEFINE_TYPE_TRAIT(ShaderNode, kShaderNode, + TYPE_ID_IMAGING_SHADER_NODE, 1); +DEFINE_TYPE_TRAIT(UsdPreviewSurface, kUsdPreviewSurface, + TYPE_ID_IMAGING_PREVIEWSURFACE, 1); +DEFINE_TYPE_TRAIT(UsdUVTexture, kUsdUVTexture, TYPE_ID_IMAGING_UVTEXTURE, 1); +DEFINE_TYPE_TRAIT(UsdPrimvarReader_float, kUsdPrimvarReader_float, + TYPE_ID_IMAGING_PRIMVAR_READER_FLOAT, 1); +DEFINE_TYPE_TRAIT(UsdPrimvarReader_float2, kUsdPrimvarReader_float2, + TYPE_ID_IMAGING_PRIMVAR_READER_FLOAT2, 1); +DEFINE_TYPE_TRAIT(UsdPrimvarReader_float3, kUsdPrimvarReader_float3, + TYPE_ID_IMAGING_PRIMVAR_READER_FLOAT3, 1); +DEFINE_TYPE_TRAIT(UsdPrimvarReader_float4, kUsdPrimvarReader_float4, + TYPE_ID_IMAGING_PRIMVAR_READER_FLOAT4, 1); +DEFINE_TYPE_TRAIT(UsdPrimvarReader_int, kUsdPrimvarReader_int, + TYPE_ID_IMAGING_PRIMVAR_READER_INT, 1); +DEFINE_TYPE_TRAIT(UsdPrimvarReader_string, kUsdPrimvarReader_string, + TYPE_ID_IMAGING_PRIMVAR_READER_STRING, 1); +DEFINE_TYPE_TRAIT(UsdPrimvarReader_vector, kUsdPrimvarReader_vector, + TYPE_ID_IMAGING_PRIMVAR_READER_VECTOR, 1); +DEFINE_TYPE_TRAIT(UsdPrimvarReader_normal, kUsdPrimvarReader_normal, + TYPE_ID_IMAGING_PRIMVAR_READER_NORMAL, 1); +DEFINE_TYPE_TRAIT(UsdPrimvarReader_point, kUsdPrimvarReader_point, + TYPE_ID_IMAGING_PRIMVAR_READER_POINT, 1); +DEFINE_TYPE_TRAIT(UsdPrimvarReader_matrix, kUsdPrimvarReader_matrix, + TYPE_ID_IMAGING_PRIMVAR_READER_MATRIX, 1); +DEFINE_TYPE_TRAIT(UsdTransform2d, kUsdTransform2d, + TYPE_ID_IMAGING_TRANSFORM_2D, 1); + +DEFINE_TYPE_TRAIT(MaterialBinding, "MaterialBindingAPI", + TYPE_ID_MATERIAL_BINDING, 1); + +#undef DEFINE_TYPE_TRAIT +#undef DEFINE_ROLE_TYPE_TRAIT + +} // namespace value + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usdSkel.cc b/contrib/tinyusdz/tinyusdz_repo/src/usdSkel.cc new file mode 100644 index 000000000..122c7297e --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usdSkel.cc @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +// UsdSkel API implementations + +#include "usdSkel.hh" + +#include + +#include "common-macros.inc" +#include "tiny-format.hh" +#include "prim-types.hh" +#include "path-util.hh" + +namespace tinyusdz { +namespace {} // namespace + +constexpr auto kInbetweensNamespace = "inbetweens"; + +bool BlendShape::add_inbetweenBlendShape(const double weight, Attribute &&attr) { + + if (attr.name().empty()) { + return false; + } + + if (attr.is_uniform()) { + return false; + } + + if (!attr.is_value()) { + return false; + } + + std::string attr_name = fmt::format("{}:{}", kInbetweensNamespace, attr.name()); + attr.set_name(attr_name); + + attr.metas().weight = weight; + + props[attr_name] = Property(attr, /* custom */false); + + return true; +} + +bool SkelAnimation::get_blendShapes(std::vector *toks) { + return blendShapes.get_value(toks); +} + +bool SkelAnimation::get_joints(std::vector *dst) { + return joints.get_value(dst); +} + +bool SkelAnimation::get_blendShapeWeights( + std::vector *vals, const double t, + const value::TimeSampleInterpolationType tinterp) { + Animatable> v; + if (blendShapeWeights.get_value(&v)) { + // Evaluate at time `t` with `tinterp` interpolation + return v.get(t, vals, tinterp); + } + + return false; +} + +bool SkelAnimation::get_rotations(std::vector *vals, + const double t, + const value::TimeSampleInterpolationType tinterp) { + Animatable> v; + if (rotations.get_value(&v)) { + // Evaluate at time `t` with `tinterp` interpolation + return v.get(t, vals, tinterp); + } + + return false; +} + +bool SkelAnimation::get_scales(std::vector *vals, const double t, + const value::TimeSampleInterpolationType tinterp) { + Animatable> v; + if (scales.get_value(&v)) { + // Evaluate at time `t` with `tinterp` interpolation + return v.get(t, vals, tinterp); + } + + return false; +} + +bool SkelAnimation::get_translations( + std::vector *vals, const double t, + const value::TimeSampleInterpolationType tinterp) { + Animatable> v; + if (translations.get_value(&v)) { + // Evaluate at time `t` with `tinterp` interpolation + return v.get(t, vals, tinterp); + } + + return false; +} + +bool BuildSkelTopology( + const std::vector &joints, + std::vector &dst, + std::string *err) { + + if (joints.empty()) { + return true; + } + + std::vector paths(joints.size()); + for (size_t i = 0; i < joints.size(); i++) { + Path p = Path(joints[i].str(), ""); + + if (!p.is_valid()) { + if (err) { + (*err) += fmt::format("joints[{}] is invalid Prim path: `{}`", i, joints[i].str()); + } + return false; + } + + if (p.is_root_path()) { + if (err) { + (*err) += fmt::format("joints[{}] Root Prim path '/' cannot be used for joint Prim path.", i); + } + return false; + } + + std::string _err; + + if (!pathutil::ValidatePrimPath(p, &_err)) { + if (err) { + (*err) += fmt::format("joints[{}] is not a valid Prim path: `{}`, reason = {}", i, joints[i].str(), _err); + } + return false; + } + + paths[i] = p; + } + + // path name <-> index map + std::map pathMap; + for (size_t i = 0; i < paths.size(); i++) { + pathMap[paths[i]] = int(i); + } + + std::vector parentIndices; + parentIndices.assign(paths.size(), -1); + + auto GetParentIndex = [](const std::map &_pathMap, const Path &path) -> int { + if (path.is_root_path()) { + return -1; + } + + // from pxrUSD's comment... + // + // Recurse over all ancestor paths, not just the direct parent. + // For instance, if the map includes only paths 'a' and 'a/b/c', + // 'a' will be treated as the parent of 'a/b/c'. + // + Path parentPath = path.get_parent_prim_path(); + + uint32_t kMaxRec = 1024 * 128; // to avoid infinite loop. + + uint32_t depth = 0; + while (parentPath.is_valid() && !parentPath.is_root_path()) { + + if (_pathMap.count(parentPath)) { + return _pathMap.at(parentPath); + } + + parentPath = parentPath.get_parent_prim_path(); + depth++; + + if (depth >= kMaxRec) { + // TODO: Report error + return -1; + } + } + + return -1; + }; + + dst.resize(joints.size()); + for (size_t i = 0; i < paths.size(); i++) { + dst[i] = GetParentIndex(pathMap, paths[i]); + } + + return true; +} + +} // namespace tinyusdz + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usdSkel.hh b/contrib/tinyusdz/tinyusdz_repo/src/usdSkel.hh new file mode 100644 index 000000000..cbf15a6f5 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usdSkel.hh @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +// UsdSkel(includes BlendShapes) +#pragma once + +#include "prim-types.hh" +#include "value-types.hh" +#include "xform.hh" + +namespace tinyusdz { + +constexpr auto kSkelRoot = "SkelRoot"; +constexpr auto kSkeleton = "Skeleton"; +constexpr auto kSkelAnimation = "SkelAnimation"; +constexpr auto kBlendShape = "BlendShape"; + +// BlendShapes +struct BlendShape { + std::string name; + Specifier spec{Specifier::Def}; + int64_t parent_id{-1}; + + void set_name(const std::string &name_) { + name = name_; + } + + const std::string &get_name() const { + return name; + } + + Specifier &specifier() { return spec; } + const Specifier &specifier() const { return spec; } + + TypedAttribute> + offsets; // uniform vector3f[]. required property + TypedAttribute> + normalOffsets; // uniform vector3f[]. required property + + TypedAttribute> + pointIndices; // uniform int[]. optional. vertex indices to the original + // mesh for each values in `offsets` and `normalOffsets`. + + std::pair> references; + std::pair> payload; + std::map variantSet; + std::map props; + + /// + /// Add attribute as in-beteen BlendShape attribute. + /// + /// - add `inbetweens` namespace prefix + /// - add `weight` attribute as Attribute meta. + /// + bool add_inbetweenBlendShape(double weight, Attribute &&attr); + + const std::vector &primChildrenNames() const { return _primChildren; } + const std::vector &propertyNames() const { return _properties; } + std::vector &primChildrenNames() { return _primChildren; } + std::vector &propertyNames() { return _properties; } + + PrimMeta meta; + + PrimMeta &metas() { + return meta; + } + + const PrimMeta &metas() const { + return meta; + } + + private: + std::vector _primChildren; + std::vector _properties; +}; + +// Skeleton +struct Skeleton : Xformable { + std::string name; + Specifier spec{Specifier::Def}; + int64_t parent_id{-1}; + + void set_name(const std::string &name_) { + name = name_; + } + + const std::string &get_name() const { + return name; + } + + Specifier &specifier() { return spec; } + const Specifier &specifier() const { return spec; } + + + TypedAttribute> + bindTransforms; // uniform matrix4d[]. bind-pose transform of each joint + // in world coordinate. + + TypedAttribute> jointNames; // uniform token[] + TypedAttribute> joints; // uniform token[] + + TypedAttribute> + restTransforms; // uniform matrix4d[] rest-pose transforms of each + // joint in local coordinate. + + nonstd::optional proxyPrim; // rel proxyPrim + + // SkelBindingAPI + nonstd::optional + animationSource; // rel skel:animationSource = + + TypedAttributeWithFallback> visibility{ + Visibility::Inherited}; // "token visibility" + TypedAttribute> + extent; // bounding extent. When authorized, the extent is the bounding + // box of whole its children. + TypedAttributeWithFallback purpose{ + Purpose::Default}; // "uniform token purpose" + + std::pair> references; + std::pair> payload; + std::map variantSet; + std::map props; + //std::vector xformOpOrder; + + PrimMeta meta; + + PrimMeta &metas() { + return meta; + } + + const PrimMeta &metas() const { + return meta; + } + + + bool get_animationSource(Path *path, ListEditQual *qual = nullptr) { + if (!path) { + return false; + } + + const Relationship &rel = animationSource.value(); + if (qual) { + (*qual) = rel.get_listedit_qual(); + } + + if (rel.is_path()) { + (*path) = rel.targetPath; + } else if (rel.is_pathvector()) { + if (rel.targetPathVector.size()) { + (*path) = rel.targetPathVector[0]; + } + } else { + return false; + } + + + return false; + } + + const std::vector &primChildrenNames() const { return _primChildren; } + const std::vector &propertyNames() const { return _properties; } + std::vector &primChildrenNames() { return _primChildren; } + std::vector &propertyNames() { return _properties; } + + private: + std::vector _primChildren; + std::vector _properties; +}; + +// NOTE: SkelRoot itself does not have dedicated attributes in the schema. +struct SkelRoot : Xformable { + std::string name; + Specifier spec{Specifier::Def}; + int64_t parent_id{-1}; + + void set_name(const std::string &name_) { + name = name_; + } + + const std::string &get_name() const { + return name; + } + + Specifier &specifier() { return spec; } + const Specifier &specifier() const { return spec; } + + + TypedAttribute> + extent; // bounding extent. When authorized, the extent is the bounding + // box of whole its children. + TypedAttributeWithFallback purpose{ + Purpose::Default}; // "uniform token purpose" + TypedAttributeWithFallback> visibility{ + Visibility::Inherited}; // "token visibility" + + nonstd::optional proxyPrim; // rel proxyPrim + //std::vector xformOps; + + // TODO: Add function to check if SkelRoot contains `Skeleton` and `GeomMesh` + // node?; + + + std::pair> references; + std::pair> payload; + std::map variantSet; + std::map props; + + const std::vector &primChildrenNames() const { return _primChildren; } + const std::vector &propertyNames() const { return _properties; } + std::vector &primChildrenNames() { return _primChildren; } + std::vector &propertyNames() { return _properties; } + + PrimMeta meta; + + PrimMeta &metas() { + return meta; + } + + const PrimMeta &metas() const { + return meta; + } + + + private: + std::vector _primChildren; + std::vector _properties; + +}; + +struct SkelAnimation { + std::string name; + Specifier spec{Specifier::Def}; + int64_t parent_id{-1}; + + void set_name(const std::string &name_) { + name = name_; + } + + const std::string &get_name() const { + return name; + } + + Specifier &specifier() { return spec; } + const Specifier &specifier() const { return spec; } + + TypedAttribute> blendShapes; // uniform token[] + TypedAttribute>> blendShapeWeights; // float[] + TypedAttribute> joints; // uniform token[] + TypedAttribute>> + rotations; // quatf[] Joint-local unit quaternion rotations + TypedAttribute>> + scales; // half3[] Joint-local scaling in 16bit half float. TODO: Use + // float3 for TinyUSDZ for convenience? + TypedAttribute>> + translations; // float3[] Joint-local translation. + + bool get_blendShapes(std::vector *toks); + bool get_blendShapeWeights(std::vector *vals, + const double t = value::TimeCode::Default(), + const value::TimeSampleInterpolationType tinterp = + value::TimeSampleInterpolationType::Held); + bool get_joints(std::vector *toks); + bool get_rotations(std::vector *vals, + const double t = value::TimeCode::Default(), + const value::TimeSampleInterpolationType tinterp = + value::TimeSampleInterpolationType::Held); + bool get_scales(std::vector *vals, + const double t = value::TimeCode::Default(), + const value::TimeSampleInterpolationType tinterp = + value::TimeSampleInterpolationType::Held); + bool get_translations(std::vector *vals, + const double t = value::TimeCode::Default(), + const value::TimeSampleInterpolationType tinterp = + value::TimeSampleInterpolationType::Held); + + std::pair> references; + std::pair> payload; + std::map variantSet; + std::map props; + + const std::vector &primChildrenNames() const { return _primChildren; } + const std::vector &propertyNames() const { return _properties; } + std::vector &primChildrenNames() { return _primChildren; } + std::vector &propertyNames() { return _properties; } + + PrimMeta meta; + + PrimMeta &metas() { + return meta; + } + + const PrimMeta &metas() const { + return meta; + } + + private: + std::vector _primChildren; + std::vector _properties; +}; + +// PackedJointAnimation is deprecated(Convert to SkelAnimation) +// struct PackedJointAnimation { +// }; + +// +// Some usdSkel utility functions +// + +// Equivalent to pxrUSd's UsdSkelNormalizeWeights +bool SkelNormalizeWeights(const std::vector &weights, int numInfluencesPerComponent, const float eps = std::numeric_limits::epsilon()); +bool SkelSortInfluences(const std::vector indices, const std::vector &weights, int numInfluencesPerComponent); + +#if 0 // move to Tydra +struct SkelNode +{ + std::string joint; + std::string jointName; + int32_t parentIndex{-1}; // Index of parent SkelNode. + int32_t index; // Index of this SkelNode. + + value::matrix4d bindTransform{value::matrix4d::identity()}; + value::matrix4d restTransform{value::matrix4d::identity()}; +}; +#endif + +// +// Build Skeleleton Topology(hierarchy) from Skeleton's joints. +// (Usually from Skeleton's `joints`attribute). +// +// If you want to get handy, full Skeleton hierarchy information, Use Tydra's BuildSkelHierarchy() API. +// +// @param[in] `joints` Joint paths +// @param[out] `dst` Built SkelTopology. dst[i] = parent joint index. -1 for root joint. +// @param[out] `err` Error message when `joints` info is invalid. +// +// @return true upon success. false when error. +// +bool BuildSkelTopology( + const std::vector &joints, + std::vector &dst, + std::string *err); + +// import DEFINE_TYPE_TRAIT and DEFINE_ROLE_TYPE_TRAIT +#include "define-type-trait.inc" + +namespace value { + +// Register usdSkel Prim type. +DEFINE_TYPE_TRAIT(SkelRoot, kSkelRoot, TYPE_ID_SKEL_ROOT, 1); +DEFINE_TYPE_TRAIT(Skeleton, kSkeleton, TYPE_ID_SKELETON, 1); +DEFINE_TYPE_TRAIT(SkelAnimation, kSkelAnimation, TYPE_ID_SKELANIMATION, 1); +DEFINE_TYPE_TRAIT(BlendShape, kBlendShape, TYPE_ID_BLENDSHAPE, 1); + +#undef DEFINE_TYPE_TRAIT +#undef DEFINE_ROLE_TYPE_TRAIT + +} // namespace value + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usda-reader.cc b/contrib/tinyusdz/tinyusdz_repo/src/usda-reader.cc new file mode 100644 index 000000000..669b85723 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usda-reader.cc @@ -0,0 +1,1786 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2021 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +// USDA reader +// TODO: +// - [ ] Refactor and unify Prim and PrimSpec related code. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ascii-parser.hh" +//#include "asset-resolution.hh" +#include "usdGeom.hh" +#include "usdSkel.hh" +#if defined(__wasi__) +#else +#include +#include +#endif +#include + +#include "usda-reader.hh" + +// +#if !defined(TINYUSDZ_DISABLE_MODULE_USDA_READER) + +// + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +// external + +#include "nonstd/expected.hpp" +#include "nonstd/optional.hpp" + +// + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// + +// Tentative +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wunused-parameter" +#endif + +#include "io-util.hh" +#include "math-util.inc" +#include "pprinter.hh" +#include "prim-types.hh" +#include "prim-reconstruct.hh" +#include "primvar.hh" +#include "str-util.hh" +#include "stream-reader.hh" +#include "tinyusdz.hh" +#include "usdObj.hh" +#include "usdShade.hh" +#include "value-pprint.hh" +#include "value-types.hh" +#include "tiny-format.hh" + +#include "common-macros.inc" + +namespace tinyusdz { + +namespace prim { + +// template specialization forward decls. +// implimentations will be located in prim-reconstruct.cc +#define RECONSTRUCT_PRIM_DECL(__ty) template<> bool ReconstructPrim<__ty>(const Specifier &spec, const PropertyMap &, const ReferenceList &, __ty *, std::string *, std::string *, const PrimReconstructOptions &) + +RECONSTRUCT_PRIM_DECL(Xform); +RECONSTRUCT_PRIM_DECL(Model); +RECONSTRUCT_PRIM_DECL(Scope); +RECONSTRUCT_PRIM_DECL(Skeleton); +RECONSTRUCT_PRIM_DECL(SkelRoot); +RECONSTRUCT_PRIM_DECL(SkelAnimation); +RECONSTRUCT_PRIM_DECL(BlendShape); +RECONSTRUCT_PRIM_DECL(DomeLight); +RECONSTRUCT_PRIM_DECL(SphereLight); +RECONSTRUCT_PRIM_DECL(CylinderLight); +RECONSTRUCT_PRIM_DECL(DiskLight); +RECONSTRUCT_PRIM_DECL(DistantLight); +RECONSTRUCT_PRIM_DECL(GPrim); +RECONSTRUCT_PRIM_DECL(GeomMesh); +RECONSTRUCT_PRIM_DECL(GeomSubset); +RECONSTRUCT_PRIM_DECL(GeomSphere); +RECONSTRUCT_PRIM_DECL(GeomPoints); +RECONSTRUCT_PRIM_DECL(GeomCone); +RECONSTRUCT_PRIM_DECL(GeomCube); +RECONSTRUCT_PRIM_DECL(GeomCylinder); +RECONSTRUCT_PRIM_DECL(GeomCapsule); +RECONSTRUCT_PRIM_DECL(GeomBasisCurves); +RECONSTRUCT_PRIM_DECL(GeomNurbsCurves); +RECONSTRUCT_PRIM_DECL(GeomCamera); +RECONSTRUCT_PRIM_DECL(PointInstancer); +RECONSTRUCT_PRIM_DECL(Material); +RECONSTRUCT_PRIM_DECL(Shader); +RECONSTRUCT_PRIM_DECL(NodeGraph); + +#undef RECONSTRUCT_PRIM_DECL + +} // namespace prim + +namespace usda { + +constexpr auto kTag = "[USDA]"; + +namespace { + +// intermediate data structure for VariantSet stmt +struct VariantNode { + PrimMeta metas; + std::map props; + std::vector primChildren; +}; + +struct PrimNode { + value::Value prim; // stores typed Prim value. Xform, GeomMesh, ... + std::string elementName; + std::string typeName; // Prim's typeName + + int64_t parent{-1}; // -1 = root node + //bool parent_is_variant{false}; // True when this Prim is defined under variantSet stmt. + std::vector children; // index to USDAReader._prims[] of childPrims. it contains variant's primChildren also. + + std::map> variantNodeMap; +}; + +// For USD scene read for composition(read by references, subLayers, payloads) +struct PrimSpecNode { + PrimSpec primSpec; + + int64_t parent{-1}; // -1 = root node + //bool parent_is_variant{false}; // True when this Prim is defined under variantSet stmt. + std::vector children; // index to USDAReader._primspecs[] + + std::map> variantNodeMap; +}; + +// TODO: Move to prim-types.hh? + +template +struct PrimTypeTraits; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-const-variable" +#endif + +#define DEFINE_PRIM_TYPE(__dty, __name, __tyid) \ + template <> \ + struct PrimTypeTraits<__dty> { \ + using primt_type = __dty; \ + static constexpr uint32_t type_id = __tyid; \ + static constexpr auto prim_type_name = __name; \ + } + +DEFINE_PRIM_TYPE(Model, "Model", value::TYPE_ID_MODEL); + +DEFINE_PRIM_TYPE(Xform, kGeomXform, value::TYPE_ID_GEOM_XFORM); +DEFINE_PRIM_TYPE(GeomMesh, kGeomMesh, value::TYPE_ID_GEOM_MESH); +DEFINE_PRIM_TYPE(GeomPoints, kGeomPoints, value::TYPE_ID_GEOM_POINTS); +DEFINE_PRIM_TYPE(GeomSphere, kGeomSphere, value::TYPE_ID_GEOM_SPHERE); +DEFINE_PRIM_TYPE(GeomCube, kGeomCube, value::TYPE_ID_GEOM_CUBE); +DEFINE_PRIM_TYPE(GeomCone, kGeomCone, value::TYPE_ID_GEOM_CONE); +DEFINE_PRIM_TYPE(GeomCapsule, kGeomCapsule, value::TYPE_ID_GEOM_CAPSULE); +DEFINE_PRIM_TYPE(GeomCylinder, kGeomCylinder, value::TYPE_ID_GEOM_CYLINDER); +DEFINE_PRIM_TYPE(GeomBasisCurves, kGeomBasisCurves, + value::TYPE_ID_GEOM_BASIS_CURVES); +DEFINE_PRIM_TYPE(GeomNurbsCurves, kGeomNurbsCurves, + value::TYPE_ID_GEOM_NURBS_CURVES); +DEFINE_PRIM_TYPE(GeomSubset, kGeomSubset, value::TYPE_ID_GEOM_GEOMSUBSET); +DEFINE_PRIM_TYPE(SphereLight, kSphereLight, value::TYPE_ID_LUX_SPHERE); +DEFINE_PRIM_TYPE(DomeLight, kDomeLight, value::TYPE_ID_LUX_DOME); +DEFINE_PRIM_TYPE(DiskLight, kDiskLight, value::TYPE_ID_LUX_DISK); +DEFINE_PRIM_TYPE(DistantLight, kDistantLight, value::TYPE_ID_LUX_DISTANT); +DEFINE_PRIM_TYPE(CylinderLight, kCylinderLight, value::TYPE_ID_LUX_CYLINDER); +DEFINE_PRIM_TYPE(Material, kMaterial, value::TYPE_ID_MATERIAL); +DEFINE_PRIM_TYPE(Shader, kShader, value::TYPE_ID_SHADER); +DEFINE_PRIM_TYPE(NodeGraph, kNodeGraph, value::TYPE_ID_NODEGRAPH); +DEFINE_PRIM_TYPE(SkelRoot, kSkelRoot, value::TYPE_ID_SKEL_ROOT); +DEFINE_PRIM_TYPE(Skeleton, kSkeleton, value::TYPE_ID_SKELETON); +DEFINE_PRIM_TYPE(SkelAnimation, kSkelAnimation, value::TYPE_ID_SKELANIMATION); +DEFINE_PRIM_TYPE(BlendShape, kBlendShape, value::TYPE_ID_BLENDSHAPE); +DEFINE_PRIM_TYPE(GeomCamera, kGeomCamera, value::TYPE_ID_GEOM_CAMERA); +DEFINE_PRIM_TYPE(PointInstancer, kPointInstancer, value::TYPE_ID_GEOM_POINT_INSTANCER); +DEFINE_PRIM_TYPE(Scope, "Scope", value::TYPE_ID_SCOPE); + +DEFINE_PRIM_TYPE(GPrim, "GPrim", value::TYPE_ID_GPRIM); + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +} // namespace + +class VariableDef { + public: + std::string type; + std::string name; + + VariableDef() = default; + + VariableDef(const std::string &t, const std::string &n) : type(t), name(n) {} + + VariableDef(const VariableDef &rhs) = default; + + VariableDef &operator=(const VariableDef &rhs) { + type = rhs.type; + name = rhs.name; + + return *this; + } +}; + +inline bool hasConnect(const std::string &str) { + return endsWith(str, ".connect"); +} + +inline bool hasInputs(const std::string &str) { + return startsWith(str, "inputs:"); +} + +inline bool hasOutputs(const std::string &str) { + return startsWith(str, "outputs:"); +} + +template +static nonstd::expected CheckAllowedTokens( + const std::vector> &allowedTokens, + const std::string &tok) { + if (allowedTokens.empty()) { + return true; + } + + for (size_t i = 0; i < allowedTokens.size(); i++) { + if (tok.compare(std::get<1>(allowedTokens[i])) == 0) { + return true; + } + } + + std::vector toks; + for (size_t i = 0; i < allowedTokens.size(); i++) { + toks.push_back(std::get<1>(allowedTokens[i])); + } + + std::string s = join(", ", tinyusdz::quote(toks)); + + return nonstd::make_unexpected("Allowed tokens are [" + s + "] but got " + + quote(tok) + "."); +}; + +template +nonstd::expected EnumHandler( + const std::string &prop_name, const std::string &tok, + const std::vector> &enums) { + auto ret = CheckAllowedTokens(enums, tok); + if (!ret) { + return nonstd::make_unexpected(ret.error()); + } + + for (auto &item : enums) { + if (tok == item.second) { + return item.first; + } + } + // Should never reach here, though. + return nonstd::make_unexpected( + quote(tok) + " is an invalid token for attribute `" + prop_name + "`"); +} + +class USDAReader::Impl { + private: + Stage _stage; + + public: + Impl(StreamReader *sr) { _parser.SetStream(sr); } + +#if 0 // TODO: Remove + // Return the flag if the .usda is read from `references` + bool IsReferenced() { return _referenced; } + + // Return the flag if the .usda is read from `subLayers` + bool IsSubLayered() { return _sub_layered; } + + // Return the flag if the .usda is read from `payload` + bool IsPayloaded() { return _payloaded; } + + // Return true if the .udsa is read in the top layer(stage) + bool IsToplevel() { + return !IsReferenced() && !IsSubLayered() && !IsPayloaded(); + } +#endif + + void SetBaseDir(const std::string &str) { _base_dir = str; } + +#if 0 + /// + /// True: create PrimSpec instead of typed Prim. + /// Set true if you do USD composition. + /// + void set_primspec_mode(bool onoff) { _primspec_mode = onoff; } +#endif + + void set_reader_config(const USDAReaderConfig &config) { + _config = config; + } + + const USDAReaderConfig get_reader_config() const { + return _config; + } + + std::string GetCurrentPath() { + if (_path_stack.empty()) { + return "/"; + } + + return _path_stack.top(); + } + + bool PathStackDepth() { return _path_stack.size(); } + + void PushPath(const std::string &p) { _path_stack.push(p); } + + void PopPath() { + if (!_path_stack.empty()) { + _path_stack.pop(); + } + } + + void PushError(const std::string &s) { + _err += s; + } + + void PushWarn(const std::string &s) { + _warn += s; + } + + template + bool ReconstructPrim( + const Specifier &spec, + const prim::PropertyMap &properties, + const prim::ReferenceList &references, + T *out); + + + template + bool RegisterReconstructCallback() { + _parser.RegisterPrimConstructFunction( + PrimTypeTraits::prim_type_name, + [&](const Path &full_path, const Specifier spec, const std::string &_primTypeName, const Path &prim_name, const int64_t primIdx, + const int64_t parentPrimIdx, + const prim::PropertyMap &properties, + const ascii::AsciiParser::PrimMetaMap &in_meta, + const ascii::AsciiParser::VariantSetList &in_variants) + -> nonstd::expected { + + std::string primTypeName = _primTypeName; + if (primTypeName == "__AnyType__") { + primTypeName = ""; // Make empty + } + + if (!prim_name.is_valid()) { + return nonstd::make_unexpected("Invalid Prim name: " + + prim_name.full_path_name()); + } + if (prim_name.is_absolute_path() || prim_name.is_root_path()) { + return nonstd::make_unexpected( + "Prim name should not starts with '/' or contain `/`: Prim " + "name = " + + prim_name.full_path_name()); + } + + if (!prim_name.prop_part().empty()) { + return nonstd::make_unexpected( + "Prim path should not contain property part(`.`): Prim name " + "= " + + prim_name.full_path_name()); + } + + if (primIdx < 0) { + return nonstd::make_unexpected( + "Unexpected primIdx value. primIdx must be positive."); + } + + T prim; + + if (!ReconstructPrimMeta(in_meta, &prim.meta)) { + return nonstd::make_unexpected( + "Failed to process Prim metadataum."); + } + + DCOUT("primType = " << value::TypeTraits::type_name() + << ", node.size " + << std::to_string(_prim_nodes.size()) + << ", primIdx = " << primIdx + << ", parentPrimIdx = " << parentPrimIdx); + + DCOUT("full_path = " << full_path.full_path_name()); + DCOUT("primName = " << prim_name.full_path_name()); + + prim::ReferenceList references; + if (prim.meta.references) { + references = prim.meta.references.value(); + } + + bool ret = ReconstructPrim(spec, properties, references, &prim); + + if (!ret) { + return nonstd::make_unexpected("Failed to reconstruct Prim: " + + prim_name.full_path_name()); + } + + prim.spec = spec; + prim.name = prim_name.prim_part(); + + // + // variants + // NOTE: variantChildren setup is delayed. It will be processed in ConstructPrimSpecTreeRec + // + std::map> variantSets; + for (const auto &variantContext : in_variants) { + const std::string variant_name = variantContext.first; + + // Convert VariantContent -> VariantNode + std::map variantNodes; + for (const auto &item : variantContext.second) { + VariantNode variant; + if (!ReconstructPrimMeta(item.second.metas, &variant.metas)) { + return nonstd::make_unexpected(fmt::format("Failed to process Prim metadataum in variantSet {} item {} ", variant_name, item.first)); + } + variant.props = item.second.props; + + // child Prim should be already reconstructed. + for (const auto &childPrimIdx : item.second.primIndices) { + if (childPrimIdx < 0) { + return nonstd::make_unexpected(fmt::format("[InternalError] Invalid primIndex found within VariantSet.")); + } + + if (size_t(childPrimIdx) >= _prim_nodes.size()) { + return nonstd::make_unexpected(fmt::format("[InternalError] Invalid primIndex found within VariantSet. variantChildPrimIdsx {} Exceeds _prim_nodes.size() {}", childPrimIdx, _prim_nodes.size())); + } + + variant.primChildren.push_back(childPrimIdx); + + //_prim_nodes[size_t(childPrimIdx)].parent_is_variant = true; + } + DCOUT("Add variant: " << item.first); + variantNodes.emplace(item.first, std::move(variant)); + } + + DCOUT("Add variantSet: " << variant_name); + variantSets.emplace(variant_name, std::move(variantNodes)); + } + + // Add to scene graph. + // NOTE: Scene graph is constructed from bottom up manner(Children + // first), so add this primIdx to parent's children. + if (size_t(primIdx) >= _prim_nodes.size()) { + _prim_nodes.resize(size_t(primIdx) + 1); + } + DCOUT("sz " << std::to_string(_prim_nodes.size()) + << ", primIdx = " << primIdx); + + _prim_nodes[size_t(primIdx)].prim = std::move(prim); + _prim_nodes[size_t(primIdx)].typeName = primTypeName; + _prim_nodes[size_t(primIdx)].variantNodeMap = variantSets; + + + // Store actual Prim typeName also for Model Prim type. + // TODO: Find more better way. + { + value::Value *p = &(_prim_nodes[size_t(primIdx)].prim); + Model *model = p->as(); + if (model) { + DCOUT("Set prim typeName " << primTypeName << " to Model Prim[" << primIdx << "]"); + model->prim_type_name = primTypeName; + } + } + + DCOUT("prim[" << primIdx << "].ty = " + << _prim_nodes[size_t(primIdx)].prim.type_name()); + _prim_nodes[size_t(primIdx)].parent = parentPrimIdx; + + if (parentPrimIdx == -1) { + _toplevel_prims.push_back(size_t(primIdx)); + } else { + _prim_nodes[size_t(parentPrimIdx)].children.push_back( + size_t(primIdx)); + } + + return true; + }); + + return true; + } + + void RegisterPrimSpecHandler() { + _parser.RegisterPrimSpecFunction( + [&](const Path &full_path, const Specifier spec, const std::string &typeName, const Path &prim_name, const int64_t primIdx, + const int64_t parentPrimIdx, + const prim::PropertyMap &properties, + const ascii::AsciiParser::PrimMetaMap &in_meta, + const ascii::AsciiParser::VariantSetList &in_variants) + -> nonstd::expected { + + if (!prim_name.is_valid()) { + return nonstd::make_unexpected("Invalid Prim name: " + + prim_name.full_path_name()); + } + if (prim_name.is_absolute_path() || prim_name.is_root_path()) { + return nonstd::make_unexpected( + "Prim name should not starts with '/' or contain `/`: Prim " + "name = " + + prim_name.full_path_name()); + } + + if (!prim_name.prop_part().empty()) { + return nonstd::make_unexpected( + "Prim path should not contain property part(`.`): Prim name " + "= " + + prim_name.full_path_name()); + } + + if (primIdx < 0) { + return nonstd::make_unexpected( + "Unexpected primIdx value. primIdx must be positive."); + } + + if (prim_name.prim_part().empty()) { + return nonstd::make_unexpected("Prim's name should not be empty "); + } + + PrimSpec primspec; + primspec.name() = prim_name.prim_part(); + primspec.specifier() = spec; + primspec.typeName() = typeName; + + DCOUT("primspec name, primType = " << prim_name.prim_part() << ", " << typeName); + + if (!ReconstructPrimMeta(in_meta, &primspec.metas())) { + return nonstd::make_unexpected( + "Failed to process Prim metadataum."); + } + + primspec.props() = properties; + + // + // variants + // NOTE: variantChildren setup is delayed. It will be processed ConstructPrimTreeRec() + // + std::map> variantSets; + for (const auto &variantContext : in_variants) { + const std::string variant_name = variantContext.first; + + // Convert VariantContent -> VariantNode + std::map variantNodes; + for (const auto &item : variantContext.second) { + VariantNode variant; + if (!ReconstructPrimMeta(item.second.metas, &variant.metas)) { + return nonstd::make_unexpected(fmt::format("Failed to process Prim metadataum in variantSet {} item {} ", variant_name, item.first)); + } + variant.props = item.second.props; + + // child Prim should be already reconstructed. + for (const auto &childPrimIdx : item.second.primIndices) { + if (childPrimIdx < 0) { + return nonstd::make_unexpected(fmt::format("[InternalError] Invalid primIndex found within VariantSet.")); + } + + if (size_t(childPrimIdx) >= _primspec_nodes.size()) { + return nonstd::make_unexpected(fmt::format("[InternalError] Invalid primIndex found within VariantSet. variantChildPrimIdsx {} Exceeds _prim_nodes.size() {}", childPrimIdx, _primspec_nodes.size())); + } + + variant.primChildren.push_back(childPrimIdx); + + //_primspec_nodes[size_t(childPrimIdx)].parent_is_variant = true; + } + DCOUT("Add variant: " << item.first); + variantNodes.emplace(item.first, std::move(variant)); + } + + DCOUT("Add variantSet: " << variant_name); + variantSets.emplace(variant_name, std::move(variantNodes)); + } + + + // Assign index for PrimSpec + // TODO: Use sample id table(= _prim_nodes) + + if (size_t(primIdx) >= _primspec_nodes.size()) { + _primspec_nodes.resize(size_t(primIdx) + 1); + } + DCOUT("sz " << std::to_string(_primspec_nodes.size()) + << ", primIdx = " << primIdx); + + _primspec_nodes[size_t(primIdx)].primSpec = std::move(primspec); + DCOUT("primspec[" << primIdx << "].ty = " + << _primspec_nodes[size_t(primIdx)].primSpec.typeName()); + _primspec_nodes[size_t(primIdx)].parent = parentPrimIdx; + _primspec_nodes[size_t(primIdx)].variantNodeMap = variantSets; + + if (parentPrimIdx == -1) { + _toplevel_primspecs.push_back(size_t(primIdx)); + } else { + _primspec_nodes[size_t(parentPrimIdx)].children.push_back( + size_t(primIdx)); + return true; + } + + return true; + } + ); + + } + + void StageMetaProcessor() { + _parser.RegisterStageMetaProcessFunction( + [&](const ascii::AsciiParser::StageMetas &metas) { + DCOUT("StageMeta CB:"); + + _stage.metas().doc = metas.doc; + if (metas.upAxis) { + _stage.metas().upAxis = metas.upAxis.value(); + } + + _stage.metas().comment = metas.comment; + + if (metas.subLayers.size()) { + // TODO subLayer offset. + std::vector sublayers; + for (size_t i = 0; i < metas.subLayers.size(); i++) { + SubLayer sublayer; + sublayer.assetPath = metas.subLayers[i]; + sublayers.push_back(sublayer); + } + _stage.metas().subLayers = sublayers; + } + + _stage.metas().defaultPrim = metas.defaultPrim; + if (metas.metersPerUnit) { + _stage.metas().metersPerUnit = metas.metersPerUnit.value(); + } + + if (metas.timeCodesPerSecond) { + _stage.metas().timeCodesPerSecond = + metas.timeCodesPerSecond.value(); + } + + if (metas.startTimeCode) { + _stage.metas().startTimeCode = metas.startTimeCode.value(); + } + + if (metas.endTimeCode) { + _stage.metas().endTimeCode = metas.endTimeCode.value(); + } + + if (metas.framesPerSecond) { + _stage.metas().framesPerSecond = metas.framesPerSecond.value(); + } + + if (metas.autoPlay) { + _stage.metas().autoPlay = metas.autoPlay.value(); + } + + if (metas.playbackMode) { + value::token tok = metas.playbackMode.value(); + if (tok.str() == "none") { + _stage.metas().playbackMode = StageMetas::PlaybackMode::PlaybackModeNone; + } else if (tok.str() == "loop") { + _stage.metas().playbackMode = StageMetas::PlaybackMode::PlaybackModeLoop; + } else { + PUSH_ERROR_AND_RETURN("Unsupported playbackMode: " + tok.str()); + } + } + + _stage.metas().customLayerData = metas.customLayerData; + + + return true; // ok + }); + } + + void RegisterPrimIdxAssignCallback() { + _parser.RegisterPrimIdxAssignFunction([&](const int64_t parentPrimIdx) { + size_t idx = _prim_nodes.size(); + + DCOUT("parentPrimIdx: " << parentPrimIdx << ", idx = " << idx); + + _prim_nodes.resize(idx + 1); + + // if (parentPrimIdx < 0) { // root + // // allocate empty prim to reserve _prim_nodes[idx] + // _prim_nodes.resize(idx + 1); + // DCOUT("resize to : " << (idx + 1)); + // } + + return idx; + }); + } + + bool ReconstructPrimMeta(const ascii::AsciiParser::PrimMetaMap &in_meta, + PrimMeta *out) { + + auto ApiSchemaHandler = [](const std::string &tok) + -> nonstd::expected { + using EnumTy = std::pair; + const std::vector enums = { + std::make_pair(APISchemas::APIName::SkelBindingAPI, "SkelBindingAPI"), + std::make_pair(APISchemas::APIName::CollectionAPI, "CollectionAPI"), + std::make_pair(APISchemas::APIName::MaterialBindingAPI, + "MaterialBindingAPI"), + std::make_pair(APISchemas::APIName::ShapingAPI, + "ShapingAPI"), + std::make_pair(APISchemas::APIName::Preliminary_PhysicsMaterialAPI, + "Preliminary_PhysicsMaterialAPI"), + std::make_pair(APISchemas::APIName::Preliminary_PhysicsRigidBodyAPI, + "Preliminary_PhysicsRigidBodyAPI"), + std::make_pair(APISchemas::APIName::Preliminary_PhysicsColliderAPI, + "Preliminary_PhysicsColliderAPI"), + std::make_pair(APISchemas::APIName::Preliminary_AnchoringAPI, + "Preliminary_AnchoringAPI") + }; + return EnumHandler("apiSchemas", tok, enums); + }; + + auto BuildVariants = [](const Dictionary &dict) -> nonstd::expected { + + // Allow empty dict. + + VariantSelectionMap m; + + for (const auto &item : dict) { + // TODO: duplicated key check? + if (auto pv = item.second.get_value()) { + m[item.first] = pv.value(); + } else if (auto pvs = item.second.get_value()) { + // TODO: store triple-quote info + m[item.first] = pvs.value().value; + } else { + return nonstd::make_unexpected(fmt::format("TinyUSDZ only accepts `string` value for `variants` element, but got type `{}`(type_id {}).", item.second.type_name(), item.second.type_id())); + } + } + + return std::move(m); + + }; + + DCOUT("ReconstructPrimMeta"); + for (const auto &meta : in_meta) { + DCOUT("meta.name = " << meta.first); + + const auto &listEditQual = std::get<0>(meta.second); + const MetaVariable &var = std::get<1>(meta.second); + + if (meta.first == "active") { + DCOUT("active. type = " << var.type_name()); + if (var.type_name() == "bool") { + if (auto pv = var.get_value()) { + out->active = pv.value(); + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `active` metadataum is not type `bool`."); + } + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `active` metadataum is not type `bool`. got `" + << var.type_name() << "`."); + } + } else if (meta.first == "hidden") { + DCOUT("hidden. type = " << var.type_name()); + if (var.type_name() == "bool") { + if (auto pv = var.get_value()) { + out->hidden = pv.value(); + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `hidden` metadataum is not type `bool`."); + } + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `hidden` metadataum is not type `bool`. got `" + << var.type_name() << "`."); + } + + } else if (meta.first == "instanceable") { + DCOUT("instanceable. type = " << var.type_name()); + if (var.type_name() == "bool") { + if (auto pv = var.get_value()) { + out->instanceable = pv.value(); + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `instanceable` metadataum is not type `bool`."); + } + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `instanceable` metadataum is not type `bool`. got `" + << var.type_name() << "`."); + } + + } else if (meta.first == "sceneName") { + DCOUT("sceneName. type = " << var.type_name()); + if (var.type_name() == value::kString) { + if (auto pv = var.get_value()) { + out->sceneName = pv.value(); + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `sceneName` metadataum is not type `string`."); + } + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `sceneName` metadataum is not type `string`. got `" + << var.type_name() << "`."); + } + } else if (meta.first == "displayName") { + DCOUT("displayName. type = " << var.type_name()); + if (var.type_name() == value::kString) { + if (auto pv = var.get_value()) { + out->displayName = pv.value(); + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `displayName` metadataum is not type `string`."); + } + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `displayName` metadataum is not type `string`. got `" + << var.type_name() << "`."); + } + } else if (meta.first == "kind") { + // std::tuple + // TODO: list-edit qual + DCOUT("kind. type = " << var.type_name()); + if (var.type_name() == "token") { + if (auto pv = var.get_value()) { + const value::token tok = pv.value(); + if (tok.str() == "subcomponent") { + out->kind = Kind::Subcomponent; + } else if (tok.str() == "component") { + out->kind = Kind::Component; + } else if (tok.str() == "model") { + out->kind = Kind::Model; + } else if (tok.str() == "group") { + out->kind = Kind::Group; + } else if (tok.str() == "assembly") { + out->kind = Kind::Assembly; + } else if (tok.str() == "sceneLibrary") { + // USDZ specific: https://developer.apple.com/documentation/arkit/usdz_schemas_for_ar/scenelibrary + out->kind = Kind::SceneLibrary; + } else { + // NOTE: empty token allowed. + + out->kind = Kind::UserDef; + out->_kind_str = tok.str(); + } + DCOUT("Added kind: " << to_string(out->kind.value())); + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `kind` metadataum is not type `token`."); + } + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `kind` metadataum is not type `token`. got `" + << var.type_name() << "`."); + } + } else if (meta.first == "sdrMetadata") { + DCOUT("sdrMetadata. type = " << var.type_name()); + if (var.type_id() == value::TypeTraits::type_id()) { + if (auto pv = var.get_value()) { + // TODO: Check if all items are string type. + out->sdrMetadata = pv.value(); + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "(Internal error?) `sdrMetadata` metadataum is not type " + "`dictionary`. got type `" + << var.type_name() << "`"); + } + + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `sdrMetadata` metadataum is not type " + "`dictionary`. got type `" + << var.type_name() << "`"); + } + } else if (meta.first == "customData") { + DCOUT("customData. type = " << var.type_name()); + if (var.type_id() == value::TypeTraits::type_id()) { + if (auto pv = var.get_value()) { + out->customData = pv.value(); + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "(Internal error?) `customData` metadataum is not type " + "`dictionary`. got type `" + << var.type_name() << "`"); + } + + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `customData` metadataum is not type " + "`dictionary`. got type `" + << var.type_name() << "`"); + } + } else if (meta.first == "clips") { + DCOUT("clips. type = " << var.type_name()); + if (var.type_id() == value::TypeTraits::type_id()) { + if (auto pv = var.get_value()) { + out->clips = pv.value(); + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "(Internal error?) `clips` metadataum is not type " + "`dictionary`. got type `" + << var.type_name() << "`"); + } + + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `clips` metadataum is not type " + "`dictionary`. got type `" + << var.type_name() << "`"); + } + } else if (meta.first == "assetInfo") { + DCOUT("assetInfo. type = " << var.type_name()); + if (auto pv = var.get_value()) { + out->assetInfo = pv.value(); + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "(Internal error?) `assetInfo` metadataum is not type " + "`dictionary`. got type `" + << var.type_name() << "`"); + } + } else if (meta.first == "variants") { + if (auto pv = var.get_value()) { + auto pm = BuildVariants(pv.value()); + if (!pm) { + PUSH_ERROR_AND_RETURN(pm.error()); + } + out->variants = (*pm); + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `variants` metadataum is not type " + "`dictionary`. got type `" + << var.type_name() << "`"); + } + } else if (meta.first == "inherits") { + if (auto pvb = var.get_value()) { + if (listEditQual != ListEditQual::ResetToExplicit) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("None or Empty list must be `explicit`(no qualifier), but has qualifier `{}`", to_string(listEditQual))); + } + out->inherits = std::make_pair(listEditQual, std::vector()); + } else if (auto pv = var.get_value>()) { + if (pv.value().empty() && (listEditQual != ListEditQual::ResetToExplicit)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("None or Empty list must be `explicit`(no qualifier), but has qualifier `{}`", to_string(listEditQual))); + } + out->inherits = std::make_pair(listEditQual, pv.value()); + } else if (auto pvp = var.get_value()) { + std::vector vs; + vs.push_back(pvp.value()); + out->inherits = std::make_pair(listEditQual, vs); + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `inherits` metadataum should be either `path` or `path[]`. " + "got type `" + << var.type_name() << "`"); + } + + } else if (meta.first == "specializes") { + if (auto pvb = var.get_value()) { + if (listEditQual != ListEditQual::ResetToExplicit) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("None or Empty list must be `explicit`(no qualifier), but has qualifier `{}`", to_string(listEditQual))); + } + out->specializes = std::make_pair(listEditQual, std::vector()); + } else if (auto pv = var.get_value>()) { + if (pv.value().empty() && (listEditQual != ListEditQual::ResetToExplicit)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("None or Empty list must be `explicit`(no qualifier), but has qualifier `{}`", to_string(listEditQual))); + } + out->specializes = std::make_pair(listEditQual, pv.value()); + } else if (auto pvp = var.get_value()) { + std::vector vs; + vs.push_back(pvp.value()); + out->specializes = std::make_pair(listEditQual, vs); + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `specializes` metadataum should be either `path` or `path[]`. " + "got type `" + << var.type_name() << "`"); + } + + } else if (meta.first == "variantSets") { + // treat as `string` + if (auto pvb = var.get_value()) { + if (listEditQual != ListEditQual::ResetToExplicit) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("None or Empty list must be `explicit`(no qualifier), but has qualifier `{}`", to_string(listEditQual))); + } + out->variantSets = std::make_pair(listEditQual, std::vector()); + } else if (auto pv = var.get_value()) { + std::vector vs; + vs.push_back(pv.value().value); + out->variantSets = std::make_pair(listEditQual, vs); + } else if (auto pvs = var.get_value()) { + std::vector vs; + vs.push_back(pvs.value()); + out->variantSets = std::make_pair(listEditQual, vs); + } else if (auto pva = var.get_value>()) { + if (pva.value().empty() && (listEditQual != ListEditQual::ResetToExplicit)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("None or Empty list must be `explicit`(no qualifier), but has qualifier `{}`", to_string(listEditQual))); + } + out->variantSets = std::make_pair(listEditQual, pva.value()); + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `variantSets` metadataum is not type " + "`string` or `string[]`. got type `" + << var.type_name() << "`"); + } + } else if (meta.first == "apiSchemas") { + DCOUT("apiSchemas. type = " << var.type_name()); + if (var.type_name() == "token[]") { + APISchemas apiSchemas; + if ((listEditQual != ListEditQual::Prepend) && (listEditQual != ListEditQual::ResetToExplicit)) { + PUSH_ERROR_AND_RETURN("(PrimMeta) " << "ListEdit op for `apiSchemas` must be empty or `prepend` in TinyUSDZ, but got `" << to_string(listEditQual) << "`"); + } + apiSchemas.listOpQual = listEditQual; + + if (auto pv = var.get_value>()) { + + for (const auto &item : pv.value()) { + // TODO: Multi-apply schema(instance name) + auto ret = ApiSchemaHandler(item.str()); + if (ret) { + apiSchemas.names.push_back({ret.value(), /* instanceName */""}); + } else { + PUSH_WARN("(PrimMeta) " << ret.error()); + } + } + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, "(Internal error?) `apiSchemas` metadataum is not type " + "`token[]`. got type `" + << var.type_name() << "`"); + } + + out->apiSchemas = std::move(apiSchemas); + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, "(Internal error?) `apiSchemas` metadataum is not type " + "`token[]`. got type `" + << var.type_name() << "`"); + } + } else if (meta.first == "references") { + + if (var.is_blocked()) { + // Treat as empty list + // empty list must be qualified as 'explicit' + if (listEditQual != ListEditQual::ResetToExplicit) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("None or Empty list must be `explicit`(no qualifier), but has qualifier `{}`", to_string(listEditQual))); + } + std::vector refs; + out->references = std::make_pair(listEditQual, refs); + } else if (auto pv = var.get_value()) { + // To Reference + std::vector refs; + refs.emplace_back(pv.value()); + out->references = std::make_pair(listEditQual, refs); + } else if (auto pva = var.get_value>()) { + if (pva.value().empty() && (listEditQual != ListEditQual::ResetToExplicit)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("None or Empty list must be `explicit`(no qualifier), but has qualifier `{}`", to_string(listEditQual))); + } + out->references = std::make_pair(listEditQual, pva.value()); + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error?) `references` metadataum is not type " + "`Reference`. got type `" + << var.type_name() << "`"); + } + } else if (meta.first == "payload") { + + if (var.is_blocked()) { + if (listEditQual != ListEditQual::ResetToExplicit) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("None or Empty list must be `explicit`(no qualifier), but has qualifier `{}`", to_string(listEditQual))); + } + // make empty + std::vector refs; + out->payload = std::make_pair(listEditQual, refs); + } else if (auto pv = var.get_value()) { + // To Payload + std::vector pls; + pls.emplace_back(pv.value()); + out->payload = std::make_pair(listEditQual, pls); + } else if (auto pva = var.get_value>()) { + if (pva.value().empty() && (listEditQual != ListEditQual::ResetToExplicit)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("None or Empty list must be `explicit`(no qualifier), but has qualifier `{}`", to_string(listEditQual))); + } + out->payload = std::make_pair(listEditQual, pva.value()); + } else { + PUSH_ERROR_AND_RETURN( + "(Internal error) `payload` metadataum is not type " + "Payload. got type `" + << var.type_name() << "`"); + } + } else if (meta.first == "comment") { + if (auto pv = var.get_value()) { + out->comment = pv.value().value; + } else if (auto spv = var.get_value()) { + out->comment = spv.value(); + } + } else { + // Must be string value for unregisteredMeta for now. + // TODO: infer int, string, token, int[], string[] and token[] type from the value for custom(unregisteredMeta) metadata. + if (auto spv = var.get_value()) { + out->unregisteredMetas[meta.first] = spv.value(); + } else { + PUSH_WARN("(Internal) unregistered Metadata must be type string for now, but got type " + var.type_name()); + } + + } + } + + return true; + } + + /// + /// Reader entry point + /// TODO: Use callback function(visitor) so that Reconstruct**** function is + /// invoked in the Parser context. + /// + bool Read(const uint32_t state_flags, bool as_primspec); + + // std::vector GetGPrims() { return _gprims; } + + std::string GetDefaultPrimName() const { return _defaultPrim; } + + std::string GetError() { return _err; } + + std::string GetWarning() { return _warn; } + + /// + /// Valid after `Read`. + /// + bool GetAsLayer(Layer *layer); + + /// + /// Valid after `Read`. + /// + bool ReconstructStage(); + + /// + /// Valid after `ReconstructStage`. + /// + const Stage &GetStage() const { return _stage; } + + + private: + //bool stage_reconstructed_{false}; + +#if 0 + /// + /// -- Iterators -- + /// + class PrimIterator { + public: + PrimIterator(const std::vector &indices, + const std::vector &values, size_t idx = 0) + : _indices(indices), _values(values), _idx(idx) {} + + const value::Value &operator*() const { return _values[_indices[_idx]]; } + + PrimIterator &operator++() { + _idx++; + return *this; + } + bool operator!=(const PrimIterator &rhs) { return _idx != rhs._idx; } + + private: + const std::vector &_indices; + const std::vector &_values; + size_t _idx{0}; + }; + friend class PrimIterator; + + // currently const only + using const_prim_iterator = const PrimIterator; + + // Iterate over toplevel prims + const_prim_iterator PrimBegin() { + return PrimIterator(_toplevel_prims, _prims); + } + const_prim_iterator PrimEnd() { + return PrimIterator(_toplevel_prims, _prims, _toplevel_prims.size()); + } + size_t PrimSize() { return _toplevel_prims.size(); } +#endif + + /// + /// -- Members -- + /// + + // TODO: Remove + // std::set _node_types; + + std::stack parse_stack; + + std::string _base_dir; // Used for importing another USD file + //AssetResolutionResolver _arr; + +#if 0 // TODO: Remove since not used. + nonstd::optional _imported_scene; // Imported scene. +#endif + + // "class" defs + //std::map _klasses; + + std::stack _path_stack; + + std::string _err; + std::string _warn; + + // Cache of loaded `references` + // + std::map> + _reference_cache; + + // toplevel prims + std::vector _toplevel_prims; // index to _prim_nodes + + // 1D Linearized array of prim nodes. + std::vector _prim_nodes; + + // Path(prim part only) -> index to _prim_nodes[] + std::map _primpath_to_prim_idx_map; + + + // toplevel primspecs + std::vector _toplevel_primspecs; // index to _prim_nodes + + // Flattened array of primspec nodes. + std::vector _primspec_nodes; + // Path(prim part only) -> index to _primspec_nodes[] + std::map _primpath_to_primspec_idx_map; + bool _primspec_invalidated{false}; + + std::string _defaultPrim; + + // Used for Ascii parser option + USDAReaderConfig _config; + + ascii::AsciiParser _parser; + +}; // namespace usda + +namespace { + +// bottom up conversion. +bool ToPrimSpecRec(const size_t primSpecIdx, + std::vector &primspec_nodes, PrimSpec &parent, std::string *err) { + + if (primSpecIdx >= primspec_nodes.size()) { + if (err) { + (*err) += "Internal error; primSpecIdx exceeds primspec_nodes.size."; + } + return false; + } + + const PrimSpecNode &node = primspec_nodes[primSpecIdx]; + + PrimSpec primspec = node.primSpec; + + // Firstly process variants. + std::set variantChildrenIndices; // record variantChildren indices + { + + std::map variantSets; + for (const auto &variantNodes : node.variantNodeMap) { + DCOUT("variantSet " << variantNodes.first); + VariantSetSpec variantSet; + for (const auto &item : variantNodes.second) { + DCOUT("variant " << item.first); + PrimSpec variant; // variantNode can be represented as PrimSpec. + for (const int64_t vidx : item.second.primChildren) { + if (variantChildrenIndices.count(vidx)) { + // Duplicated variant childrenIndices + if (err) { + (*err) = fmt::format("variant primIdx {} is referenced multiple times.\n", vidx); + } + return false; + } else { + // Add prim to variants + if ((vidx >= 0) && (size_t(vidx) <= primspec_nodes.size())) { + + PrimSpec variantChildPrim; // dummy + if (!ToPrimSpecRec(size_t(vidx), primspec_nodes, variantChildPrim, err)) { + return false; + } + + DCOUT(fmt::format("Added prim {} to variantSet {} : variant {}", variantChildPrim.name(), variantNodes.first, item.first)); + variant.children().emplace_back(variantChildPrim); + } else { + if (err) { + (*err) = "primIndex exceeds prim_nodes.size()\n"; + } + return false; + } + + variantChildrenIndices.insert(vidx); + } + } + + variant.metas() = std::move(item.second.metas); + variant.props() = std::move(item.second.props); + + variantSet.name = variantNodes.first; + variantSet.variantSet.emplace(item.first, std::move(variant)); + } + DCOUT(fmt::format("Add {} to variantSet", variantNodes.first)); + variantSets.emplace(variantNodes.first, std::move(variantSet)); + } + primspec.variantSets() = std::move(variantSets); + } + + for (const auto &cidx : node.children) { + + if (variantChildrenIndices.count(int64_t(cidx))) { + // PrimSpec is already processed + continue; + } + + PrimSpec childPrimSpec; + if (!ToPrimSpecRec(cidx, primspec_nodes, childPrimSpec, err)) { + return false; + } + primspec.children().emplace_back(std::move(childPrimSpec)); + } + + parent = std::move(primspec); + + return true; +} + +} // namespace + +bool USDAReader::Impl::GetAsLayer(Layer *layer) { + + if (!layer) { + PUSH_ERROR_AND_RETURN("layer arg is nullptr."); + } + + if (_primspec_invalidated) { + PUSH_ERROR_AND_RETURN("PrimSpec data is invalid. USD data is not loaded or there was an error in earlier GetAsLayer call, or GetAsLayer was invoked multiple times."); + } + + layer->clear_primspecs(); + DCOUT("# of subLayers = " << _stage.metas().subLayers.size()); + layer->metas() = _stage.metas(); + + for (const auto &idx : _toplevel_primspecs) { + DCOUT("Toplevel primspec idx: " << std::to_string(idx)); + + if (idx >= _primspec_nodes.size()) { + PUSH_ERROR_AND_RETURN("[Internal Error] out-of-bounds access."); + } + + auto &node = _primspec_nodes[idx]; + PrimSpec &primSpec = node.primSpec; + + DCOUT("primspec[" << idx << "].typeName = " << primSpec.typeName()); + DCOUT("primspec[" << idx << "].name = " << primSpec.name()); + DCOUT("root prim[" << idx << "].num_children = " << primSpec.children().size()); + + if (!ToPrimSpecRec(idx, _primspec_nodes, /* inout */primSpec, &_err)) { + _primspec_invalidated = true; + PUSH_ERROR_AND_RETURN("Construct PrimSpec tree failed."); + } + + if (!layer->emplace_primspec(primSpec.name(), std::move(_primspec_nodes[idx].primSpec))) { + PUSH_ERROR_AND_RETURN(fmt::format("Construct PrimSpec tree failed: PrimSpec.name = {}", primSpec.name())); + } + } + + // NOTE: _toplevel_primspecs are destroyed(std::move'ed) + _primspec_invalidated = true; + + return true; +} + +/// +/// -- Impl reconstruct +// + +namespace { + +// +// Construct Prim from PrimNode with botom-up approach +// +bool ConstructPrimTreeRec(const size_t primIdx, + const std::vector &prim_nodes, + Prim *destPrim, + std::string *err) { + + if (!destPrim) { + if (err) { + (*err) = "`destPrim` is nullptr.\n"; + } + return false; + } + + if (primIdx >= prim_nodes.size()) { + if (err) { + (*err) = "primIndex exceeds prim_nodes.size()\n"; + } + return false; + } + + const auto &node = prim_nodes[primIdx]; + + Prim prim(node.prim); + prim.prim_type_name() = node.typeName; + + DCOUT("prim[" << primIdx << "].type = " << node.prim.type_name()); + DCOUT("prim[" << primIdx << "].variantNodeMap.size = " << node.variantNodeMap.size()); + //prim.prim_id() = int64_t(idx); + + // Firstly process variants. + std::set variantChildrenIndices; // record variantChildren indices + + std::map variantSets; + for (const auto &variantNodes : node.variantNodeMap) { + DCOUT("variantSet " << variantNodes.first); + VariantSet variantSet; + for (const auto &item : variantNodes.second) { + DCOUT("variant " << item.first); + Variant variant; + for (const int64_t vidx : item.second.primChildren) { + if (variantChildrenIndices.count(vidx)) { + // Duplicated variant childrenIndices + if (err) { + (*err) = fmt::format("variant primIdx {} is referenced multiple times.\n", vidx); + } + return false; + } else { + // Add prim to variants + if ((vidx >= 0) && (size_t(vidx) <= prim_nodes.size())) { + + Prim variantChildPrim(value::Value(nullptr)); // dummy + if (!ConstructPrimTreeRec(size_t(vidx), prim_nodes, &variantChildPrim, err)) { + return false; + } + + DCOUT(fmt::format("Added prim {} to variantSet {} : variant {}", variantChildPrim.element_name(), variantNodes.first, item.first)); + variant.primChildren().emplace_back(variantChildPrim); + } else { + if (err) { + (*err) = "primIndex exceeds prim_nodes.size()\n"; + } + return false; + } + + variantChildrenIndices.insert(vidx); + } + } + variant.metas() = std::move(item.second.metas); + variant.properties() = std::move(item.second.props); + + variantSet.name = variantNodes.first; + variantSet.variantSet.emplace(item.first, std::move(variant)); + } + variantSets.emplace(variantNodes.first, std::move(variantSet)); + } + prim.variantSets() = std::move(variantSets); + + for (const auto &cidx : node.children) { + if (variantChildrenIndices.count(int64_t(cidx))) { + // Prim is processed + continue; + } + + Prim childPrim(value::Value(nullptr)); // dummy + if (!ConstructPrimTreeRec(cidx, prim_nodes, &childPrim, err)) { + return false; + } + + prim.children().emplace_back(std::move(childPrim)); + } + + (*destPrim) = std::move(prim); + return true; +} + +} // namespace + + + +bool USDAReader::Impl::ReconstructStage() { + _stage.root_prims().clear(); + + for (const auto &idx : _toplevel_prims) { + DCOUT("Toplevel prim idx: " << std::to_string(idx)); + + Prim prim(value::Value(nullptr)); // init with dummy Prim + if (!ConstructPrimTreeRec(idx, _prim_nodes, &prim, &_err)) { + return false; + } + + _stage.root_prims().emplace_back(std::move(prim)); + + DCOUT("num_children = " << _stage.root_prims()[size_t(_stage.root_prims().size() - 1)].children().size()); + } + + // Compute Abs Path from built Prim tree and Assign prim id. + _stage.compute_absolute_prim_path_and_assign_prim_id(); + + return true; +} + +template <> +bool USDAReader::Impl::ReconstructPrim( + const Specifier &spec, + const prim::PropertyMap &properties, + const prim::ReferenceList &references, + Xform *xform) { + + std::string err; + if (!prim::ReconstructPrim(spec, properties, references, xform, &_warn, &err)) { + PUSH_ERROR_AND_RETURN("Failed to reconstruct Xform Prim: " << err); + } + return true; +} + +#if 0 +/// +/// -- RegisterReconstructCallback specializations +/// + +template <> +bool USDAReader::Impl::ReconstructPrim( + const Specifier &spec, + const prim::PropertyMap &properties, + const prim::ReferenceList &references, + GPrim *gprim) { + (void)spec; + (void)gprim; + + DCOUT("TODO: Reconstruct GPrim."); + + PUSH_WARN("TODO: Reconstruct GPrim."); + + return true; +} + + +template <> +bool USDAReader::Impl::ReconstructPrim( + const Specifier &spec, + const prim::PropertyMap &properties, + const prim::ReferenceList &references, + NodeGraph *graph) { + (void)properties; + (void)references; + (void)graph; + + PUSH_WARN("TODO: reconstruct NodeGrah."); + + return true; +} +#endif + +// Generic Prim handler. T = Xform, GeomMesh, ... +template +bool USDAReader::Impl::ReconstructPrim( + const Specifier &spec, + const prim::PropertyMap &properties, + const prim::ReferenceList &references, + T *prim) { + + prim::PrimReconstructOptions options; + options.strict_allowedToken_check = _config.strict_allowedToken_check; + DCOUT("strict_allowedToken_check " << options.strict_allowedToken_check); + + std::string err; + if (!prim::ReconstructPrim(spec, properties, references, prim, &_warn, &err, options)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to reconstruct {} Prim: {}", value::TypeTraits::type_name(), err)); + } + return true; +} + +/// +/// -- Impl callback specializations +/// + +/// +/// -- Impl Read +/// + +bool USDAReader::Impl::Read(const uint32_t state_flags, bool as_primspec) { + + /// + /// Convert parser option. + /// + ascii::AsciiParserOption ascii_parser_option; + ascii_parser_option.allow_unknown_prim = _config.allow_unknown_prims; + ascii_parser_option.allow_unknown_apiSchema = _config.allow_unknown_apiSchema; + ascii_parser_option.strict_allowedToken_check = _config.strict_allowedToken_check; + + /// + /// Setup callbacks. + /// + StageMetaProcessor(); + + RegisterPrimIdxAssignCallback(); + + // For composition(as_primspec == true) + RegisterPrimSpecHandler(); + + // For direct Prim reconstruction(load state = Toplevel) + RegisterReconstructCallback(); // Generic prim. + + RegisterReconstructCallback(); // Geometric prim + + RegisterReconstructCallback(); + RegisterReconstructCallback(); + RegisterReconstructCallback(); + RegisterReconstructCallback(); + RegisterReconstructCallback(); + RegisterReconstructCallback(); + RegisterReconstructCallback(); + RegisterReconstructCallback(); + RegisterReconstructCallback(); + RegisterReconstructCallback(); + RegisterReconstructCallback(); + RegisterReconstructCallback(); + + RegisterReconstructCallback(); + RegisterReconstructCallback(); + + RegisterReconstructCallback(); + + RegisterReconstructCallback(); + RegisterReconstructCallback(); + RegisterReconstructCallback(); + RegisterReconstructCallback(); + RegisterReconstructCallback(); + + RegisterReconstructCallback(); + RegisterReconstructCallback(); + RegisterReconstructCallback(); + RegisterReconstructCallback(); + + _parser.set_primspec_mode(as_primspec); + + bool ret = _parser.Parse(state_flags, ascii_parser_option); + + std::string warn = _parser.GetWarning(); + if (!warn.empty()) { + PUSH_WARN(" " + warn); + } + + if (!ret) { + PUSH_ERROR_AND_RETURN("Parse failed:\n" + _parser.GetError()); + } + + + return true; +} + +// +// -- +// + +bool IsUSDA(const std::string &filename, size_t max_filesize) { + // TODO: Read only first N bytes + std::vector data; + std::string err; + + if (!io::ReadWholeFile(&data, &err, filename, max_filesize)) { + return false; + } + + tinyusdz::StreamReader sr(data.data(), data.size(), /* swap endian */ false); + tinyusdz::ascii::AsciiParser parser(&sr); + + return parser.CheckHeader(); +} + +/// +/// -- USDAReader +/// +USDAReader::USDAReader(StreamReader *sr) { _impl = new Impl(sr); } + +USDAReader::~USDAReader() { delete _impl; } + +bool USDAReader::read(const uint32_t state_flags, bool as_primspec) { + return _impl->Read(state_flags, as_primspec); +} + +void USDAReader::set_base_dir(const std::string &dir) { + return _impl->SetBaseDir(dir); +} + +// std::vector USDAReader::GetGPrims() { return _impl->GetGPrims(); } + +//std::string USDAReader::GetDefaultPrimName() const { +// return _impl->GetDefaultPrimName(); +//} + +std::string USDAReader::get_error() { return _impl->GetError(); } +std::string USDAReader::get_warning() { return _impl->GetWarning(); } + +bool USDAReader::get_as_layer(Layer *layer) { return _impl->GetAsLayer(layer); } + +bool USDAReader::reconstruct_stage() { return _impl->ReconstructStage(); } + +const Stage &USDAReader::get_stage() const { return _impl->GetStage(); } + +void USDAReader::set_reader_config(const USDAReaderConfig &config) { + return _impl->set_reader_config(config); +} + +const USDAReaderConfig USDAReader::get_reader_config() const { + return _impl->get_reader_config(); +} + +} // namespace usda +} // namespace tinyusdz + +#else + +namespace tinyusdz { +namespace usda { + +USDAReader::USDAReader(StreamReader *sr) { + _empty_stage = new Stage(); + (void)sr; +} + +USDAReader::~USDAReader() { + delete _empty_stage; + _empty_stage = nullptr; +} + +bool USDAReader::check_header() { return false; } + +bool USDAReader::read(const LoadState state, bool as_primspec) { + (void)state; + (void)as_primspec; + return false; +} + +void USDAReader::set_base_dir(const std::string &dir) { (void)dir; } + +//std::vector USDAReader::GetGPrims() { return {}; } + +//std::string USDAReader::GetDefaultPrimName() const { return std::string{}; } + +std::string USDAReader::get_error() { + return "USDA parser feature is disabled in this build.\n"; +} +std::string USDAReader::get_warning() { return std::string{}; } +bool USDAReader::reconstruct_stage() { return false; } + +bool USDAReader::get_as_layer(Layer *layer) { return false; } + +const Stage &USDAReader::get_stage() const { + return *_empty_stage; +} + +void USDAReader::set_reader_config(const USDAReaderConfig &config) { + (void)config; +} + +USDAReaderConfig USDAReader::get_reader_config() const { + return USDAReaderConfig(); +} + +} // namespace usda +} // namespace tinyusdz + +#endif diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usda-reader.hh b/contrib/tinyusdz/tinyusdz_repo/src/usda-reader.hh new file mode 100644 index 000000000..82416b91f --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usda-reader.hh @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2021 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +#pragma once + +#include "tinyusdz.hh" +#include "stream-reader.hh" + +#include "ascii-parser.hh" +//#include "asset-resolution.hh" + +namespace tinyusdz { + +namespace usda { + +struct USDAReaderConfig { + bool allow_unknown_prims{true}; + bool allow_unknown_shader{true}; + bool allow_unknown_apiSchema{true}; + bool strict_allowedToken_check{false}; +}; + +/// +/// Test if input file is USDA format. +/// +bool IsUSDA(const std::string &filename, size_t max_filesize = 0); + +class USDAReader { + public: + struct ParseState { + int64_t loc{-1}; // byte location in StreamReder + }; + + USDAReader() = delete; + USDAReader(tinyusdz::StreamReader *sr); + + USDAReader(const USDAReader &rhs) = delete; + USDAReader(USDAReader &&rhs) = delete; + + ~USDAReader(); + + + /// + /// Base filesystem directory to search asset files. + /// TODO: Not used so remove it. + /// + void set_base_dir(const std::string &base_dir); + void SetBaseDir(const std::string &base_dir) { // Deprecared + set_base_dir(base_dir); + } + + /// + /// Set AssetResolution resolver. + /// + //void set_asset_resolution_resolver(const AssetResolutionResolver &arr); + + /// + /// Set reader option + /// + void set_reader_config(const USDAReaderConfig &config); + + /// + /// Get reader option + /// + const USDAReaderConfig get_reader_config() const; // NOTE: Not returning reference to avoid static memory allocation. + + /// + /// Check if header data is USDA + /// + bool check_header(); + bool CheckHeader() { // Deprecated + return check_header(); + } + + /// + /// Reader entry point + /// + /// `as_primspec` : Create PrimSpec instead of concrete(typed) Prim. Set true if you want to do composition + /// + bool read(uint32_t load_state = static_cast(LoadState::Toplevel), bool as_primspec = false); + bool Read(LoadState state = LoadState::Toplevel, bool as_primspec = false) { // Deprecated + uint32_t ustate = static_cast(state); + return read(ustate, as_primspec); + } + + /// + /// Get error message(when reading USDA failed) + /// + std::string get_error(); + std::string GetError() { // Deprecated + return get_error(); + } + + /// + /// Get warning message. + /// + std::string get_warning(); + std::string GetWarning() { // Deprecated + return get_warning(); + } + + /// + /// Get read USD scene data as Layer + /// Must be called after `read` + /// + /// FIXME: Currently concrete(typed) Prims are not included in destination Layer. + /// If you use this function, you'll need to invoke `read` with `as_primspec=true`. + /// + /// + bool get_as_layer(Layer *layer); + bool GetAsLayer(Layer *layer) { // Deprecated + return get_as_layer(layer); + } + + /// + /// Reconstruct Stage from loaded USD scene data. + /// Must be called after `Read` + /// + bool reconstruct_stage(); + bool ReconstructStage() { // Deprecated + return reconstruct_stage(); + } + + /// + /// Get as stage(scene graph). Must call `ReconstructStage` beforehand. + /// + const Stage& get_stage() const; + const Stage& GetStage() const { // Deprecated + return get_stage(); + } + + private: +#if defined(TINYUSDZ_DISABLE_MODULE_USDA_READER) + Stage *_empty_stage{nullptr}; +#else + class Impl; + Impl *_impl{nullptr}; +#endif + +}; + +} // namespace usda + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usda-writer.cc b/contrib/tinyusdz/tinyusdz_repo/src/usda-writer.cc new file mode 100644 index 000000000..8a802dbae --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usda-writer.cc @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +// USDA(Ascii) writer +// + +#include "usda-writer.hh" + +#if !defined(TINYUSDZ_DISABLE_MODULE_USDA_WRITER) + +#include +#include +#include + +#include "pprinter.hh" +#include "value-pprint.hh" +#include "tinyusdz.hh" +#include "io-util.hh" + +namespace tinyusdz { +namespace usda { + +namespace { + + +} // namespace + +bool SaveAsUSDA(const std::string &filename, const Stage &stage, + std::string *warn, std::string *err) { + + (void)warn; + + // TODO: Handle warn and err on export. + std::string s = stage.ExportToString(); + + if (!io::WriteWholeFile(filename, reinterpret_cast(s.data()), s.size(), err)) { + return false; + } + + std::cout << "Wrote to [" << filename << "]\n"; + + return true; +} + +#if defined(_WIN32) +bool SaveAsUSDA(const std::wstring &filename, const Stage &stage, + std::string *warn, std::string *err) { + + (void)warn; + + // TODO: Handle warn and err on export. + std::string s = stage.ExportToString(); + + if (!io::WriteWholeFile(filename, reinterpret_cast(s.data()), s.size(), err)) { + return false; + } + + std::wcout << "Wrote to [" << filename << "]\n"; + + return true; +} +#endif + +} // namespace usda +} // namespace tinyusdz + + +#else + +namespace tinyusdz { +namespace usda { + +bool SaveAsUSDA(const std::string &filename, const Stage &stage, std::string *warn, std::string *err) { + (void)filename; + (void)stage; + (void)warn; + + if (err) { + (*err) = "USDA Writer feature is disabled in this build.\n"; + } + return false; +} + + + +} // namespace usda +} // namespace tinyusdz +#endif + + diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usda-writer.hh b/contrib/tinyusdz/tinyusdz_repo/src/usda-writer.hh new file mode 100644 index 000000000..b3c329cff --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usda-writer.hh @@ -0,0 +1,26 @@ +#pragma once + +#include "tinyusdz.hh" + +namespace tinyusdz { +namespace usda { + +/// +/// Save scene as USDA(ASCII) +/// +/// @param[in] filename USDA filename(UTF-8). WideChar(Unicode) represented as std::string is supported on Windows. +/// @param[in] stage Stage(scene graph). +/// @param[out] warn Warning message +/// @param[out] err Error message +/// +/// @return true upon success. +/// +bool SaveAsUSDA(const std::string &filename, const Stage &stage, std::string *warn, std::string *err); + +#if defined(_WIN32) +// WideChar(UNICODE) filename version. +bool SaveAsUSDA(const std::wstring &filename, const Stage &stage, std::string *warn, std::string *err); +#endif + +} // namespace usda +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usdc-reader.cc b/contrib/tinyusdz/tinyusdz_repo/src/usdc-reader.cc new file mode 100644 index 000000000..5f513a6d9 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usdc-reader.cc @@ -0,0 +1,3872 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2020 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +// +// USDC(Crate) reader +// +// TODO: +// +// - [ ] Validate the existence of connection Paths(Connection) and target +// Paths(Relation) +// - [ ] GeomSubset +// - [ ] Refactor Variant related code. +// + +#ifdef _MSC_VER +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + +#include "usdc-reader.hh" + +#if !defined(TINYUSDZ_DISABLE_MODULE_USDC_READER) + +#include +#include +#include + +#include "prim-types.hh" +#include "tinyusdz.hh" +#include "value-types.hh" +#if defined(__wasi__) +#else +#include +#endif + +#include "crate-format.hh" +#include "crate-pprint.hh" +#include "crate-reader.hh" +#include "integerCoding.h" +#include "lz4-compression.hh" +#include "path-util.hh" +#include "pprinter.hh" +#include "prim-reconstruct.hh" +#include "str-util.hh" +#include "stream-reader.hh" +#include "tiny-format.hh" +#include "value-pprint.hh" +#include "usdShade.hh" +#include "ascii-parser.hh" + +// +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#include "nonstd/expected.hpp" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// +#include "common-macros.inc" + +namespace tinyusdz { + +namespace prim { + +// template specialization forward decls. +// implimentations will be located in prim-reconstruct.cc +#define RECONSTRUCT_PRIM_DECL(__ty) \ + template <> \ + bool ReconstructPrim<__ty>(const Specifier &spec, const PropertyMap &, const ReferenceList &, \ + __ty *, std::string *, std::string *, const PrimReconstructOptions &) + +RECONSTRUCT_PRIM_DECL(Xform); +RECONSTRUCT_PRIM_DECL(Model); +RECONSTRUCT_PRIM_DECL(Scope); +RECONSTRUCT_PRIM_DECL(GeomPoints); +RECONSTRUCT_PRIM_DECL(GeomMesh); +RECONSTRUCT_PRIM_DECL(GeomCapsule); +RECONSTRUCT_PRIM_DECL(GeomCube); +RECONSTRUCT_PRIM_DECL(GeomCone); +RECONSTRUCT_PRIM_DECL(GeomCylinder); +RECONSTRUCT_PRIM_DECL(GeomSphere); +RECONSTRUCT_PRIM_DECL(GeomSubset); +RECONSTRUCT_PRIM_DECL(GeomBasisCurves); +RECONSTRUCT_PRIM_DECL(GeomNurbsCurves); +RECONSTRUCT_PRIM_DECL(GeomCamera); +RECONSTRUCT_PRIM_DECL(PointInstancer); +RECONSTRUCT_PRIM_DECL(SphereLight); +RECONSTRUCT_PRIM_DECL(DomeLight); +RECONSTRUCT_PRIM_DECL(DiskLight); +RECONSTRUCT_PRIM_DECL(DistantLight); +RECONSTRUCT_PRIM_DECL(CylinderLight); +RECONSTRUCT_PRIM_DECL(SkelRoot); +RECONSTRUCT_PRIM_DECL(SkelAnimation); +RECONSTRUCT_PRIM_DECL(Skeleton); +RECONSTRUCT_PRIM_DECL(BlendShape); +RECONSTRUCT_PRIM_DECL(Material); +RECONSTRUCT_PRIM_DECL(Shader); + +#undef RECONSTRUCT_PRIM_DECL + +} // namespace prim + +namespace usdc { + +constexpr auto kTag = "[USDC]"; + +// TODO: Unify with ascii-parser.cc +static void RegisterPrimAttrTypes(std::set &d) { + d.clear(); + + d.insert(value::kBool); + + d.insert(value::kInt64); + + d.insert(value::kInt); + d.insert(value::kInt2); + d.insert(value::kInt3); + d.insert(value::kInt4); + + d.insert(value::kUInt64); + + d.insert(value::kUInt); + d.insert(value::kUInt2); + d.insert(value::kUInt3); + d.insert(value::kUInt4); + + d.insert(value::kFloat); + d.insert(value::kFloat2); + d.insert(value::kFloat3); + d.insert(value::kFloat4); + + d.insert(value::kDouble); + d.insert(value::kDouble2); + d.insert(value::kDouble3); + d.insert(value::kDouble4); + + d.insert(value::kHalf); + d.insert(value::kHalf2); + d.insert(value::kHalf3); + d.insert(value::kHalf4); + + d.insert(value::kQuath); + d.insert(value::kQuatf); + d.insert(value::kQuatd); + + d.insert(value::kNormal3f); + d.insert(value::kPoint3f); + d.insert(value::kTexCoord2h); + d.insert(value::kTexCoord3h); + d.insert(value::kTexCoord4h); + d.insert(value::kTexCoord2f); + d.insert(value::kTexCoord3f); + d.insert(value::kTexCoord4f); + d.insert(value::kTexCoord2d); + d.insert(value::kTexCoord3d); + d.insert(value::kTexCoord4d); + d.insert(value::kVector3f); + d.insert(value::kVector4f); + d.insert(value::kColor3h); + d.insert(value::kColor3f); + d.insert(value::kColor3d); + d.insert(value::kColor4h); + d.insert(value::kColor4f); + d.insert(value::kColor4d); + + // Allow `matrixNf` type for USDC + d.insert(value::kMatrix2f); + d.insert(value::kMatrix3f); + d.insert(value::kMatrix4f); + + d.insert(value::kMatrix2d); + d.insert(value::kMatrix3d); + d.insert(value::kMatrix4d); + + d.insert(value::kToken); + d.insert(value::kString); + + d.insert(value::kRelationship); + d.insert(value::kAssetPath); + + d.insert(value::kDictionary); + + // variantSet. Require special treatment. + // d.insert("variantSet"); + + // TODO: Add more types... +} + + +static bool IsUnregisteredValueType(const std::string &typeName) +{ + std::string tyname = typeName; + + //bool array_type = false; + if (endsWith(typeName, "[]")) { + tyname = removeSuffix(typeName, "[]"); + //array_type = true; + } + + // TODO: Define in crate-format? + if (tyname == value::TypeTraits::type_name()) { + return true; + } + if (tyname == value::TypeTraits::type_name()) { + return true; + } + if (tyname == value::TypeTraits::type_name()) { + return true; + } + + return false; + +} + +class USDCReader::Impl { + public: + Impl(StreamReader *sr, const USDCReaderConfig &config) : _sr(sr) { + set_reader_config(config); + RegisterPrimAttrTypes(_supported_prim_attr_types); + } + + ~Impl() { + delete crate_reader; + crate_reader = nullptr; + } + + void set_reader_config(const USDCReaderConfig &config) { + _config = config; + +#if defined(__wasi__) + _config.numThreads = 1; +#else + if (_config.numThreads == -1) { + _config.numThreads = + (std::max)(1, int(std::thread::hardware_concurrency())); + } + // Limit to 1024 threads. + _config.numThreads = (std::min)(1024, _config.numThreads); +#endif + } + + const USDCReaderConfig get_reader_config() const { + return _config; + } + + bool ReadUSDC(); + + using PathIndexToSpecIndexMap = std::unordered_map; + + /// + /// Construct Property(Attribute, Relationship/Connection) from + /// FieldValuePairs + /// + bool ParseProperty(const SpecType specType, + const crate::FieldValuePairVector &fvs, Property *prop); + + /// + /// Parse Prim spec from FieldValuePairs + /// + bool ParsePrimSpec(const crate::FieldValuePairVector &fvs, + nonstd::optional &typeName, /* out */ + nonstd::optional &specifier, /* out */ + std::vector &primChildren, /* out */ + std::vector &properties, /* out */ + PrimMeta &primMeta); /* out */ + + bool ParseVariantSetFields( + const crate::FieldValuePairVector &fvs, + std::vector &variantChildren); /* out */ + + template + bool ReconstructPrim(const Specifier &spec, const crate::CrateReader::Node &node, + const PathIndexToSpecIndexMap &psmap, T *prim); + + /// + /// Reconstrcut Prim node. + /// Returns reconstruct Prim to `primOut` + /// When `current` is 0(StageMeta), `primOut` is not set. + /// `is_parent_variant` : True when parent path is Variant + /// + bool ReconstructPrimNode(int parent, int current, int level, + bool is_parent_variant, + const PathIndexToSpecIndexMap &psmap, Stage *stage, + nonstd::optional *primOut); + + /// + /// Reconstrcut PrimSpec node. + /// Returns reconstruct PrimSpec to `primOut` + /// When `current` is 0(StageMeta), `primOut` is not set. + /// `is_parent_variant` : True when parent path is Variant + /// + /// TODO: Unify code with ReconstructPrimNode. + /// + bool ReconstructPrimSpecNode(int parent, int current, int level, + bool is_parent_variant, + const PathIndexToSpecIndexMap &psmap, Layer *layer, + nonstd::optional *primOut); + + /// + /// Reconstruct Prim from given `typeName` string(e.g. "Xform") + /// + /// @param[out] is_unsupported_prim true when encounter Unsupported Prim type(and returns nullopt) + /// + nonstd::optional ReconstructPrimFromTypeName( + const std::string &typeName, const std::string &primTypeName, const std::string &prim_name, + const crate::CrateReader::Node &node, const Specifier spec, + const std::vector &primChildren, + const std::vector &properties, + const PathIndexToSpecIndexMap &psmap, const PrimMeta &meta, bool *is_unsupported_prim = nullptr); + + bool ReconstructPrimRecursively(int parent_id, int current_id, Prim *rootPrim, + int level, + const PathIndexToSpecIndexMap &psmap, + Stage *stage); + + //bool ReconstructPrimTree(Prim *rootPrim, const PathIndexToSpecIndexMap &psmap, + // Stage *stage); + + bool ReconstructStage(Stage *stage); + + /// + /// For Layer + /// + + bool ReconstructPrimSpecRecursively(int parent_id, int current_id, PrimSpec *rootPrim, + int level, + const PathIndexToSpecIndexMap &psmap, + Layer *stage); + + //bool ReconstructPrimSpecTree(PrimSpec *rootPrim, const PathIndexToSpecIndexMap &psmap, + // Layer *layer); + + bool ToLayer(Layer *layer); + + /// + /// -------------------------------------------------- + /// + + void PushError(const std::string &s) { _err = s + _err; } + + void PushWarn(const std::string &s) { _warn = s + _warn; } + + std::string GetError() { return _err; } + + std::string GetWarning() { return _warn; } + + // Approximated memory usage in [mb] + size_t GetMemoryUsage() const { return memory_used / (1024 * 1024); } + + private: + nonstd::expected ToAPISchemas( + const ListOp &); + + // ListOp -> (ListEditOp, [T]) + template + std::vector>> DecodeListOp( + const ListOp &); + + /// + /// Builds std::map from the list of Path(Spec) + /// indices. + /// + bool BuildPropertyMap(const std::vector &pathIndices, + const PathIndexToSpecIndexMap &psmap, + prim::PropertyMap *props); + + bool ReconstrcutStageMeta(const crate::FieldValuePairVector &fvs, + StageMetas *out); + + bool AddVariantChildrenToPrimNode( + int32_t prim_idx, const std::vector &variantChildren) { + if (prim_idx < 0) { + return false; + } + + if (_variantChildren.count(uint32_t(prim_idx))) { + PUSH_WARN("Multiple Field with VariantSet SpecType detected."); + } + + _variantChildren[uint32_t(prim_idx)] = variantChildren; + + return true; + } + + bool AddVariantToPrimNode(int32_t prim_idx, const value::Value &variant); + + crate::CrateReader *crate_reader{nullptr}; + + StreamReader *_sr = nullptr; + std::string _err; + std::string _warn; + + USDCReaderConfig _config; + + // Tracks the memory used(In advisorily manner since counting memory usage is + // done by manually, so not all memory consumption could be tracked) + size_t memory_used{0}; // in bytes. + + nonstd::optional GetPath(crate::Index index) const { + if (index.value < _paths.size()) { + return _paths[index.value]; + } + + return nonstd::nullopt; + } + + nonstd::optional GetElemPath(crate::Index index) const { + if (index.value < _elemPaths.size()) { + return _elemPaths[index.value]; + } + + return nonstd::nullopt; + } + + // TODO: Do not copy data from crate_reader. + std::vector _nodes; + std::vector _specs; + std::vector _fields; + std::vector _fieldset_indices; + std::vector _string_indices; + std::vector _paths; + std::vector _elemPaths; + + std::map + _live_fieldsets; //

+ + // std::vector _prim_nodes; + + // VariantSet Spec. variantChildren + std::map> _variantChildren; + + // For Prim/Props defined as Variant(SpecType::VariantSet) + // key = path index. + std::map _variantPrims; // For Stage + std::map _variantPrimSpecs; // For Layer + std::map> _variantProps; + std::map _variants; + + // key = parent path index, values = key to `_variantPrims`, `_variantProps` + std::map> _variantPrimChildren; + std::map> _variantPropChildren; + + // Check if given node_id is a prim node. + std::set _prim_table; + + std::set _supported_prim_attr_types; +}; + +// +// -- Impl +// + +#if 0 + +bool USDCReader::Impl::ReconstructGeomSubset( + const Node &node, const FieldValuePairVector &fields, + const std::unordered_map &path_index_to_spec_index_map, + GeomSubset *geom_subset) { + + DCOUT("Reconstruct GeomSubset"); + + for (const auto &fv : fields) { + if (fv.first == "properties") { + FIELDVALUE_DATATYPE_CHECK(fv, "properties", crate::kTokenVector) + + // for (size_t i = 0; i < fv.second.GetStringArray().size(); i++) { + // // if (fv.second.GetStringArray()[i] == "points") { + // // } + // } + } + } + + for (size_t i = 0; i < node.GetChildren().size(); i++) { + int child_index = int(node.GetChildren()[i]); + if ((child_index < 0) || (child_index >= int(_nodes.size()))) { + PUSH_ERROR("Invalid child node id: " + std::to_string(child_index) + + ". Must be in range [0, " + std::to_string(_nodes.size()) + + ")"); + return false; + } + + // const Node &child_node = _nodes[size_t(child_index)]; + + if (!path_index_to_spec_index_map.count(uint32_t(child_index))) { + // No specifier assigned to this child node. + // TODO: Should we report an error? + continue; + } + + uint32_t spec_index = + path_index_to_spec_index_map.at(uint32_t(child_index)); + if (spec_index >= _specs.size()) { + PUSH_ERROR("Invalid specifier id: " + std::to_string(spec_index) + + ". Must be in range [0, " + std::to_string(_specs.size()) + + ")"); + return false; + } + + const crate::Spec &spec = _specs[spec_index]; + + Path path = GetPath(spec.path_index); + DCOUT("Path prim part: " << path.prim_part() + << ", prop part: " << path.prop_part() + << ", spec_index = " << spec_index); + + if (!_live_fieldsets.count(spec.fieldset_index)) { + _err += "FieldSet id: " + std::to_string(spec.fieldset_index.value) + + " must exist in live fieldsets.\n"; + return false; + } + + const FieldValuePairVector &child_fields = + _live_fieldsets.at(spec.fieldset_index); + + { + std::string prop_name = path.prop_part(); + + Attribute attr; + bool ret = ParseAttribute(child_fields, &attr, prop_name); + DCOUT("prop: " << prop_name << ", ret = " << ret); + + if (ret) { + // TODO(syoyo): Support more prop names + if (prop_name == "elementType") { + auto p = attr.var.get_value(); + if (p) { + std::string str = p->str(); + if (str == "face") { + geom_subset->elementType = GeomSubset::ElementType::Face; + } else { + PUSH_ERROR("`elementType` must be `face`, but got `" + str + "`"); + return false; + } + } else { + PUSH_ERROR("`elementType` must be token type, but got " + + value::GetTypeName(attr.var.type_id())); + return false; + } + } else if (prop_name == "faces") { + auto p = attr.var.get_value>(); + if (p) { + geom_subset->faces = (*p); + } + + DCOUT("faces.num = " << geom_subset->faces.size()); + + } else { + // Assume Primvar. + if (geom_subset->attribs.count(prop_name)) { + _err += "Duplicated property name found: " + prop_name + "\n"; + return false; + } + +#ifdef TINYUSDZ_LOCAL_DEBUG_PRINT + std::cout << "add [" << prop_name << "] to generic attrs\n"; +#endif + + geom_subset->attribs[prop_name] = std::move(attr); + } + } + } + } + + return true; +} + +#endif + +namespace {} + +nonstd::expected USDCReader::Impl::ToAPISchemas( + const ListOp &arg) { + APISchemas schemas; + + auto SchemaHandler = + [](const value::token &tok) -> nonstd::optional { + if (tok.str() == "MaterialBindingAPI") { + return APISchemas::APIName::MaterialBindingAPI; + } else if (tok.str() == "SkelBindingAPI") { + return APISchemas::APIName::SkelBindingAPI; + } else if (tok.str() == "Preliminary_AnchoringAPI") { + return APISchemas::APIName::Preliminary_AnchoringAPI; + } else if (tok.str() == "Preliminary_PhysicsColliderAPI") { + return APISchemas::APIName::Preliminary_PhysicsColliderAPI; + } else if (tok.str() == "Preliminary_PhysicsMaterialAPI") { + return APISchemas::APIName::Preliminary_PhysicsMaterialAPI; + } else if (tok.str() == "Preliminary_PhysicsRigidBodyAPI") { + return APISchemas::APIName::Preliminary_PhysicsRigidBodyAPI; + } else { + return nonstd::nullopt; + } + }; + + if (arg.IsExplicit()) { // fast path + for (auto &item : arg.GetExplicitItems()) { + if (auto pv = SchemaHandler(item)) { + std::string instanceName = ""; // TODO + schemas.names.push_back({pv.value(), instanceName}); + } else { + return nonstd::make_unexpected("Invalid or Unsupported API schema: " + + item.str()); + } + } + schemas.listOpQual = ListEditQual::ResetToExplicit; + + } else { + // Assume all items have same ListEdit qualifier. + if (arg.GetExplicitItems().size()) { + if (arg.GetAddedItems().size() || arg.GetAppendedItems().size() || + arg.GetDeletedItems().size() || arg.GetPrependedItems().size() || + arg.GetOrderedItems().size()) { + return nonstd::make_unexpected( + "Currently TinyUSDZ does not support ListOp with different " + "ListEdit qualifiers."); + } + for (auto &&item : arg.GetExplicitItems()) { + if (auto pv = SchemaHandler(item)) { + std::string instanceName = ""; // TODO + schemas.names.push_back({pv.value(), instanceName}); + } else { + return nonstd::make_unexpected("Invalid or Unsupported API schema: " + + item.str()); + } + } + schemas.listOpQual = ListEditQual::ResetToExplicit; + + } else if (arg.GetAddedItems().size()) { + if (arg.GetExplicitItems().size() || arg.GetAppendedItems().size() || + arg.GetDeletedItems().size() || arg.GetPrependedItems().size() || + arg.GetOrderedItems().size()) { + return nonstd::make_unexpected( + "Currently TinyUSDZ does not support ListOp with different " + "ListEdit qualifiers."); + } + for (auto &item : arg.GetAddedItems()) { + if (auto pv = SchemaHandler(item)) { + std::string instanceName = ""; // TODO + schemas.names.push_back({pv.value(), instanceName}); + } else { + return nonstd::make_unexpected("Invalid or Unsupported API schema: " + + item.str()); + } + } + schemas.listOpQual = ListEditQual::Add; + } else if (arg.GetAppendedItems().size()) { + if (arg.GetExplicitItems().size() || arg.GetAddedItems().size() || + arg.GetDeletedItems().size() || arg.GetPrependedItems().size() || + arg.GetOrderedItems().size()) { + return nonstd::make_unexpected( + "Currently TinyUSDZ does not support ListOp with different " + "ListEdit qualifiers."); + } + for (auto &&item : arg.GetAppendedItems()) { + if (auto pv = SchemaHandler(item)) { + std::string instanceName = ""; // TODO + schemas.names.push_back({pv.value(), instanceName}); + } else { + return nonstd::make_unexpected("Invalid or Unsupported API schema: " + + item.str()); + } + } + schemas.listOpQual = ListEditQual::Append; + } else if (arg.GetDeletedItems().size()) { + if (arg.GetExplicitItems().size() || arg.GetAddedItems().size() || + arg.GetAppendedItems().size() || arg.GetPrependedItems().size() || + arg.GetOrderedItems().size()) { + return nonstd::make_unexpected( + "Currently TinyUSDZ does not support ListOp with different " + "ListEdit qualifiers."); + } + for (auto &&item : arg.GetDeletedItems()) { + if (auto pv = SchemaHandler(item)) { + std::string instanceName = ""; // TODO + schemas.names.push_back({pv.value(), instanceName}); + } else { + return nonstd::make_unexpected("Invalid or Unsupported API schema: " + + item.str()); + } + } + schemas.listOpQual = ListEditQual::Delete; + } else if (arg.GetPrependedItems().size()) { + if (arg.GetExplicitItems().size() || arg.GetAddedItems().size() || + arg.GetAppendedItems().size() || arg.GetDeletedItems().size() || + arg.GetOrderedItems().size()) { + return nonstd::make_unexpected( + "Currently TinyUSDZ does not support ListOp with different " + "ListEdit qualifiers."); + } + for (auto &&item : arg.GetPrependedItems()) { + if (auto pv = SchemaHandler(item)) { + std::string instanceName = ""; // TODO + schemas.names.push_back({pv.value(), instanceName}); + } else { + return nonstd::make_unexpected("Invalid or Unsupported API schema: " + + item.str()); + } + } + schemas.listOpQual = ListEditQual::Prepend; + } else if (arg.GetOrderedItems().size()) { + if (arg.GetExplicitItems().size() || arg.GetAddedItems().size() || + arg.GetAppendedItems().size() || arg.GetDeletedItems().size() || + arg.GetPrependedItems().size()) { + return nonstd::make_unexpected( + "Currently TinyUSDZ does not support ListOp with different " + "ListEdit qualifiers."); + } + + // schemas.qual = ListEditQual::Order; + return nonstd::make_unexpected("TODO: Ordered ListOp items."); + } else { + // ??? This should not happend. + return nonstd::make_unexpected("Internal error: ListOp conversion."); + } + } + + return std::move(schemas); +} + +template +std::vector>> +USDCReader::Impl::DecodeListOp(const ListOp &arg) { + std::vector>> dst; + + if (arg.IsExplicit()) { // fast path + dst.push_back({ListEditQual::ResetToExplicit, arg.GetExplicitItems()}); + } else { + // Assume all items have same ListEdit qualifier. + if (arg.GetExplicitItems().size()) { + dst.push_back({ListEditQual::ResetToExplicit, arg.GetExplicitItems()}); + } + if (arg.GetAddedItems().size()) { + dst.push_back({ListEditQual::Add, arg.GetAddedItems()}); + } + if (arg.GetAppendedItems().size()) { + dst.push_back({ListEditQual::Append, arg.GetAppendedItems()}); + } + if (arg.GetDeletedItems().size()) { + dst.push_back({ListEditQual::Delete, arg.GetDeletedItems()}); + } + if (arg.GetPrependedItems().size()) { + dst.push_back({ListEditQual::Prepend, arg.GetPrependedItems()}); + } + if (arg.GetOrderedItems().size()) { + dst.push_back({ListEditQual::Order, arg.GetOrderedItems()}); + } + } + + return std::move(dst); +} + +bool USDCReader::Impl::BuildPropertyMap(const std::vector &pathIndices, + const PathIndexToSpecIndexMap &psmap, + prim::PropertyMap *props) { + for (size_t i = 0; i < pathIndices.size(); i++) { + int child_index = int(pathIndices[i]); + if ((child_index < 0) || (child_index >= int(_nodes.size()))) { + PUSH_ERROR("Invalid child node id: " + std::to_string(child_index) + + ". Must be in range [0, " + std::to_string(_nodes.size()) + + ")"); + return false; + } + + if (!psmap.count(uint32_t(child_index))) { + // No specifier assigned to this child node. + // Should we report an error? + continue; + } + + uint32_t spec_index = psmap.at(uint32_t(child_index)); + if (spec_index >= _specs.size()) { + PUSH_ERROR("Invalid specifier id: " + std::to_string(spec_index) + + ". Must be in range [0, " + std::to_string(_specs.size()) + + ")"); + return false; + } + + const crate::Spec &spec = _specs[spec_index]; + + // Property must be Attribute or Relationship + if ((spec.spec_type == SpecType::Attribute) || + (spec.spec_type == SpecType::Relationship)) { + // OK + } else { + continue; + } + + nonstd::optional path = GetPath(spec.path_index); + + if (!path) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid PathIndex."); + } + + DCOUT("Path prim part: " << path.value().prim_part() + << ", prop part: " << path.value().prop_part() + << ", spec_index = " << spec_index); + + if (!_live_fieldsets.count(spec.fieldset_index)) { + PUSH_ERROR("FieldSet id: " + std::to_string(spec.fieldset_index.value) + + " must exist in live fieldsets."); + return false; + } + + const crate::FieldValuePairVector &child_fvs = + _live_fieldsets.at(spec.fieldset_index); + + { + std::string prop_name = path.value().prop_part(); + if (prop_name.empty()) { + DCOUT("path = " << dump_path(path.value())); + // ??? + PUSH_ERROR_AND_RETURN_TAG(kTag, "Property Prop.PropPart is empty"); + } + + std::string prop_err; + if (!pathutil::ValidatePropPath(Path("", prop_name), &prop_err)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Invalid Property name `{}`: {}", prop_name, prop_err)); + } + + Property prop; + if (!ParseProperty(spec.spec_type, child_fvs, &prop)) { + PUSH_ERROR_AND_RETURN_TAG( + kTag, + fmt::format( + "Failed to construct Property `{}` from FieldValuePairVector.", + prop_name)); + } + + (*props)[prop_name] = prop; + DCOUT("Add property : " << prop_name); + } + } + + return true; +} + + +/// Attrib/Property fieldSet example +/// +/// specTyppe = SpecTypeConnection +/// +/// - typeName(token) : type name of Attribute(e.g. `float`) +/// - custom(bool) : `custom` qualifier +/// - variability(variability) : Variability(meta?) +/// +/// - default : Default(fallback) value. +/// - timeSample(TimeSamples) : `.timeSamples` data. +/// - connectionPaths(type = ListOpPath) : `.connect` +/// - (Empty) : Define only(Neiher connection nor value assigned. e.g. +/// "float outputs:rgb") +bool USDCReader::Impl::ParseProperty(const SpecType spec_type, + const crate::FieldValuePairVector &fvs, + Property *prop) { + if (fvs.size() > _config.kMaxFieldValuePairs) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Too much FieldValue pairs."); + } + + bool custom{false}; + nonstd::optional typeName; + nonstd::optional interpolation; + nonstd::optional elementSize; + nonstd::optional hidden; + nonstd::optional customData; + nonstd::optional weight; + nonstd::optional bindMaterialAs; + nonstd::optional connectability; + nonstd::optional renderType; + nonstd::optional outputName; + nonstd::optional sdrMetadata; + nonstd::optional comment; + nonstd::optional variability; + AttrMeta meta; // for other not frequently-used attribute/relationship metadata. + Property::Type propType{Property::Type::EmptyAttrib}; + Attribute attr; + + bool has_default{false}; + bool has_timesamples{false}; + + value::Value scalar; + Relationship rel; + + // for consistency check + bool hasConnectionChildren{false}; + bool hasConnectionPaths{false}; + bool hasTargetChildren{false}; + bool hasTargetPaths{false}; + + DCOUT("== List of Fields"); + + // first detect typeName + for (auto &fv : fvs) { + if (fv.first == "typeName") { + if (auto pv = fv.second.get_value()) { + DCOUT(" typeName = " << pv.value().str()); + typeName = pv.value(); + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "`typeName` field is not `token` type."); + } + } + } + + for (auto &fv : fvs) { + DCOUT(" fv name " << fv.first << "(type = " << fv.second.type_name() + << ")"); + + if (fv.first == "custom") { + if (auto pv = fv.second.get_value()) { + custom = pv.value(); + DCOUT(" custom = " << pv.value()); + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, "`custom` field is not `bool` type."); + } + } else if (fv.first == "variability") { + if (auto pv = fv.second.get_value()) { + variability = pv.value(); + DCOUT(" variability = " << to_string(variability.value())); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`variability` field is not `varibility` type."); + } + } else if (fv.first == "typeName") { + // 'typeName' is already processed. nothing to do here. + continue; + } else if (fv.first == "default") { + propType = Property::Type::Attrib; + + // Set scalar + // TODO: Easier CrateValue to Attribute.var conversion + scalar = fv.second.get_raw(); + has_default = true; + + // TODO: Handle UnregisteredValue in crate-reader.cc + // UnregisteredValue is represented as string. + if (const auto pv = scalar.get_value()) { + if (typeName && (typeName.value().str() != "string")) { + if (IsUnregisteredValueType(typeName.value().str())) { + DCOUT("UnregisteredValue type: " << typeName.value().str()); + + std::string local_err; + if (!ascii::ParseUnregistredValue(typeName.value().str(), pv.value(), &scalar, &local_err)) { + PUSH_ERROR_AND_RETURN(fmt::format("Failed to parse UnregisteredValue string with type `{}`: {}", typeName.value().str(), local_err)); + } + } + } + } + + } else if (fv.first == "timeSamples") { + propType = Property::Type::Attrib; + + if (auto pv = fv.second.get_value()) { + primvar::PrimVar var; + var.set_timesamples(pv.value()); + attr.set_var(std::move(var)); + has_timesamples = true; + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "`timeSamples` is not TimeSamples data."); + } + } else if (fv.first == "interpolation") { + propType = Property::Type::Attrib; + + if (auto pv = fv.second.get_value()) { + DCOUT(" interpolation = " << pv.value().str()); + + if (auto interp = InterpolationFromString(pv.value().str())) { + interpolation = interp.value(); + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid token for `interpolation`."); + } + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "`interpolation` field is not `token` type."); + } + } else if (fv.first == "connectionPaths") { + // .connect + propType = Property::Type::Connection; + hasConnectionPaths = true; + + if (auto pv = fv.second.get_value>()) { + auto p = pv.value(); + DCOUT("connectionPaths = " << to_string(p)); + + if (!p.IsExplicit()) { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`connectionPaths` must be composed of Explicit items."); + } + + // Must be explicit_items for now. + auto items = p.GetExplicitItems(); + if (items.size() == 0) { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`connectionPaths` have empty Explicit items."); + } + + if (items.size() == 1) { + // Single + const Path path = items[0]; + + rel.set(path); + + } else { + rel.set(items); // [Path] + } + + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`connectionPaths` field is not `ListOp[Path]` type."); + } + } else if (fv.first == "targetPaths") { + // `rel` + propType = Property::Type::Relation; + hasTargetPaths = true; + + if (auto pv = fv.second.get_value>()) { + const ListOp &p = pv.value(); + DCOUT("targetPaths = " << to_string(p)); + + auto ps = DecodeListOp(p); + + if (ps.empty()) { + // Empty `targetPaths` + PUSH_ERROR_AND_RETURN_TAG(kTag, "`targetPaths` is empty."); + } + + if (ps.size() > 1) { + // This should not happen though. + PUSH_WARN( + "ListOp with multiple ListOpType is not supported for now. Use " + "the first one: " + + to_string(std::get<0>(ps[0]))); + } + + auto qual = std::get<0>(ps[0]); + auto items = std::get<1>(ps[0]); + + if (items.size() == 1) { + // Single + const Path path = items[0]; + + rel.set(path); + + } else { + rel.set(items); // [Path] + } + + rel.set_listedit_qual(qual); + + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`targetPaths` field is not `ListOp[Path]` type."); + } + + } else if (fv.first == "hidden") { + // Attribute hidden param + if (auto pv = fv.second.get_value()) { + auto p = pv.value(); + DCOUT("hidden = " << to_string(p)); + + hidden = p; + + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "`elementSize` field is not `int` type."); + } + } else if (fv.first == "elementSize") { + // Attribute Meta + if (auto pv = fv.second.get_value()) { + auto p = pv.value(); + DCOUT("elementSize = " << to_string(p)); + + if ((p < 1) || (uint32_t(p) > _config.kMaxElementSize)) { + PUSH_ERROR_AND_RETURN_TAG( + kTag, + fmt::format("`elementSize` must be within [{}, {}), but got {}", + 1, _config.kMaxElementSize, p)); + } + + elementSize = p; + + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "`elementSize` field is not `int` type."); + } + } else if (fv.first == "weight") { + // pxrUSD uses float type. + if (auto pv = fv.second.get_value()) { + auto p = pv.value(); + DCOUT("weight = " << p); + + weight = double(p); + + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "`weight` field is not `float` type."); + } + } else if (fv.first == "bindMaterialAs") { + // Attribute Meta + if (auto pv = fv.second.get_value()) { + auto p = pv.value(); + DCOUT("bindMaterialAs = " << to_string(p)); + + if ((p.str() == kWeaderThanDescendants) || (p.str() == kStrongerThanDescendants)) { + // ok + } else { + // still any token is valid(for future usecase) + PUSH_WARN("Unsupported bindMaterialAs token: " << p.str()); + } + bindMaterialAs = p; + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "`bindMaterialAs` field is not `token` type."); + } + } else if (fv.first == "targetChildren") { + // `targetChildren` seems optionally exist to validate the existence of + // target Paths when `targetPaths` field exists. + // TODO: validate path of `targetChildren` + hasTargetChildren = true; + + // Path vector + if (auto pv = fv.second.get_value>()) { + DCOUT("targetChildren = " << pv.value()); + // PUSH_WARN("TODO: targetChildren"); + + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`targetChildren` field is not `PathVector` type."); + } + } else if (fv.first == "connectionChildren") { + // `connectionChildren` seems optionally exist to validate the existence + // of connection Paths when `connectiontPaths` field exists. + // TODO: validate path of `connetionChildren` + hasConnectionChildren = true; + + // Path vector + if (auto pv = fv.second.get_value>()) { + DCOUT("connectionChildren = " << pv.value()); + // PUSH_WARN("TODO: connectionChildren"); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`connectionChildren` field is not `PathVector` type."); + } + } else if (fv.first == "connectability") { + if (auto pv = fv.second.get_value()) { + connectability = pv.value(); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`connectability` must be type `token`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "outputName") { + if (auto pv = fv.second.get_value()) { + outputName = pv.value(); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`outputName` must be type `token`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "renderType") { + if (auto pv = fv.second.get_value()) { + renderType = pv.value(); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`renderType` must be type `token`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "sdrMetadata") { + if (auto pv = fv.second.get_value()) { + sdrMetadata = pv.value(); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`sdrMetadata` must be type `dictionary`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "customData") { + // CustomData(dict) + if (auto pv = fv.second.get_value()) { + customData = pv.value(); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`customData` must be type `dictionary`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "comment") { + if (auto pv = fv.second.get_value()) { + value::StringData s; + s.value = pv.value(); + s.is_triple_quoted = hasNewline(s.value); + comment = s; + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`comment` must be type `string`, but got type `" + << fv.second.type_name() << "`"); + } + + } else if (fv.first == "colorSpace") { + if (auto pv = fv.second.get_value()) { + + MetaVariable mv; + mv.set_name("colorSpace"); + mv.set_value(pv.value()); + + meta.meta["colorSpace"] = mv; + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`colorSpace` must be type `token`, but got type `" + << fv.second.type_name() << "`"); + } + } else { + // TODO: register unkown metadataum as custom metadata? + PUSH_WARN("TODO: " << fv.first); + DCOUT("TODO: " << fv.first); + } + } + DCOUT("== End List of Fields"); + + // Post check +#if 0 + if (hasConnectionChildren) { + // Validate existence of Path.. + } + + if (hasTargetChildren) { + // Validate existence of Path.. + } +#else + (void)hasTargetPaths; + (void)hasTargetChildren; + (void)hasConnectionChildren; + (void)hasConnectionPaths; +#endif + + if (has_default) { + if (typeName) { + if (scalar.type_id() == value::TypeTraits::type_id()) { + // nothing to do + } else { + std::string reqTy = typeName.value().str(); + std::string scalarTy = scalar.type_name(); + + if (reqTy.compare(scalarTy) != 0) { + + // Some inlined? value uses less accuracy type(e.g. `half3`) than + // typeName(e.g. `float3`) Use type specified in `typeName` as much as + // possible. + bool ret = value::UpcastType(reqTy, scalar); + if (ret) { + DCOUT(fmt::format("Upcast type from {} to {}.", scalarTy, reqTy)); + } + + // Optionally, cast to role type(in crate data, `typeName` uses role typename(e.g. `color3f`), whereas stored data uses base typename(e.g. VEC3F) + scalarTy = scalar.type_name(); + if (value::RoleTypeCast(value::GetTypeId(reqTy), scalar)) { + DCOUT(fmt::format("Casted to Role type {} from type {}.", reqTy, scalarTy)); + } else { + // Its ok. + } + } + } + } + + if (has_timesamples) { + DCOUT("add scalar"); + // overwrite + primvar::PrimVar var = attr.get_var(); + var.set_value(scalar); + DCOUT("var.is_timesamples = " << var.is_timesamples()); + attr.set_var(std::move(var)); + + } else { + primvar::PrimVar var; + var.set_value(scalar); + attr.set_var(std::move(var)); + } + + if (scalar.type_id() == value::TypeTraits::type_id()) { + if (typeName) { + // Use `typeName` + attr.set_type_name(typeName.value().str()); + } + } + } + + // Attribute metas + if (interpolation) { + meta.interpolation = interpolation.value(); + } + if (elementSize) { + meta.elementSize = elementSize.value(); + } + if (hidden) { + meta.hidden = hidden.value(); + } + if (customData) { + meta.customData = customData.value(); + } + if (weight) { + meta.weight = weight.value(); + } + if (comment) { + meta.comment = comment.value(); + } + if (bindMaterialAs) { + meta.bindMaterialAs = bindMaterialAs.value(); + } + if (outputName) { + meta.outputName = outputName.value(); + } + if (sdrMetadata) { + meta.sdrMetadata = sdrMetadata.value(); + } + if (connectability) { + meta.connectability = connectability.value(); + } + if (renderType) { + meta.renderType = renderType.value(); + } + + + + // FIXME: SpecType supercedes propType. + if (propType == Property::Type::EmptyAttrib) { + if (!prop) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Internal error. prop is nullptr."); + } + + if (typeName) { + // typeName may be array type. + std::string baseTypeName = typeName.value().str(); + if (endsWith(baseTypeName, "[]")) { + baseTypeName = removeSuffix(baseTypeName, "[]"); + } + + // Assume Attribute + if (!_supported_prim_attr_types.count(baseTypeName)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Invalid or unsupported `typeName` {}", typeName.value())); + } + + Property p; + p.set_property_type(Property::Type::EmptyAttrib); + p.attribute().set_type_name(typeName.value().str()); + p.set_custom(custom); + + if (variability) { + p.attribute().variability() = variability.value(); + } + p.attribute().metas() = meta; + + (*prop) = p; + + } else { + DCOUT("spec_type = " << to_string(spec_type)); + if (spec_type == SpecType::Relationship) { + // `rel` with no target. e.g. `rel target` + rel = Relationship(); + rel.set_novalue(); + if (variability == Variability::Varying) { + rel.set_varying_authored(); + } + rel.metas() = meta; + (*prop) = Property(rel, custom); + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, "`typeName` field is missing."); + } + } + } else if (propType == Property::Type::Attrib) { + + if (!prop) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Internal error. prop is nullptr."); + } + + if (variability) { + attr.variability() = variability.value(); + } + attr.metas() = meta; + (*prop) = Property(attr, custom); + } else if (propType == Property::Type::Connection) { + + if (!prop) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Internal error. prop is nullptr."); + } + + if (!typeName) { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`typeName` field is missing for Attribute Connection."); + } + if (rel.is_path()) { + (*prop) = Property(rel.targetPath, typeName.value().str(), custom); + } else if (rel.is_pathvector()) { + (*prop) = Property(rel.targetPathVector, typeName.value().str(), custom); + } else { + // ??? + PUSH_ERROR_AND_RETURN_TAG(kTag, "TODO:"); + } + + prop->attribute().metas() = meta; + } else if (propType == Property::Type::Relation) { + + if (!prop) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Internal error. prop is nullptr."); + } + + if (variability) { + if (variability.value() == Variability::Varying) { + rel.set_varying_authored(); + } + } + rel.metas() = meta; + (*prop) = Property(rel, custom); + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, "TODO:"); + } + + return true; +} + +template +bool USDCReader::Impl::ReconstructPrim(const Specifier &spec, const crate::CrateReader::Node &node, + const PathIndexToSpecIndexMap &psmap, + T *prim) { + // Prim's properties are stored in its children nodes. + prim::PropertyMap properties; + if (!BuildPropertyMap(node.GetChildren(), psmap, &properties)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to build PropertyMap."); + } + + prim::ReferenceList refs; // dummy + + prim::PrimReconstructOptions reconstruct_options; + reconstruct_options.strict_allowedToken_check = _config.strict_allowedToken_check; + + if (!prim::ReconstructPrim(spec, properties, refs, prim, &_warn, &_err, reconstruct_options)) { + return false; + } + + return true; +} + +bool USDCReader::Impl::ReconstrcutStageMeta( + const crate::FieldValuePairVector &fvs, StageMetas *metas) { + /// Stage(toplevel layer) Meta fieldSet example. + /// + /// specTy = SpecTypePseudoRoot + /// + /// - subLayers(+ subLayerOffsets) + /// - customLayerData(dict) + /// - defaultPrim(token) + /// - metersPerUnit(double) + /// - timeCodesPerSecond(double) + /// - upAxis(token) + /// - documentation(string) : `doc` + /// - comment(string) : comment + /// - primChildren(token[]) : Crate only. List of root prims(Root Prim should be traversed based on this array) + + std::vector subLayers; + std::vector subLayerOffsets; + + for (const auto &fv : fvs) { + if (fv.first == "upAxis") { + auto vt = fv.second.get_value(); + if (!vt) { + PUSH_ERROR_AND_RETURN("`upAxis` must be `token` type."); + } + + std::string v = vt.value().str(); + if (v == "Y") { + metas->upAxis = Axis::Y; + } else if (v == "Z") { + metas->upAxis = Axis::Z; + } else if (v == "X") { + metas->upAxis = Axis::X; + } else { + PUSH_ERROR_AND_RETURN("`upAxis` must be 'X', 'Y' or 'Z' but got '" + v + + "'(note: Case sensitive)"); + } + DCOUT("upAxis = " << to_string(metas->upAxis.get_value())); + + } else if (fv.first == "metersPerUnit") { + if (auto vf = fv.second.get_value()) { + metas->metersPerUnit = double(vf.value()); + } else if (auto vd = fv.second.get_value()) { + metas->metersPerUnit = vd.value(); + } else { + PUSH_ERROR_AND_RETURN( + "`metersPerUnit` value must be double or float type, but got '" + + fv.second.type_name() + "'"); + } + DCOUT("metersPerUnit = " << metas->metersPerUnit.get_value()); + } else if (fv.first == "timeCodesPerSecond") { + if (auto vf = fv.second.get_value()) { + metas->timeCodesPerSecond = double(vf.value()); + } else if (auto vd = fv.second.get_value()) { + metas->timeCodesPerSecond = vd.value(); + } else { + PUSH_ERROR_AND_RETURN( + "`timeCodesPerSecond` value must be double or float " + "type, but got '" + + fv.second.type_name() + "'"); + } + DCOUT("timeCodesPerSecond = " << metas->timeCodesPerSecond.get_value()); + } else if (fv.first == "startTimeCode") { + if (auto vf = fv.second.get_value()) { + metas->startTimeCode = double(vf.value()); + } else if (auto vd = fv.second.get_value()) { + metas->startTimeCode = vd.value(); + } else { + PUSH_ERROR_AND_RETURN( + "`startTimeCode` value must be double or float " + "type, but got '" + + fv.second.type_name() + "'"); + } + DCOUT("startimeCode = " << metas->startTimeCode.get_value()); + } else if (fv.first == "subLayers") { + if (auto vs = fv.second.get_value>()) { + subLayers = vs.value(); + } else { + PUSH_ERROR_AND_RETURN( + "`subLayers` value must be string[] " + "type, but got '" + + fv.second.type_name() + "'"); + } + } else if (fv.first == "subLayerOffsets") { + if (auto vs = fv.second.get_value>()) { + subLayerOffsets = vs.value(); + } else { + PUSH_ERROR_AND_RETURN( + "`subLayerOffsets` value must be LayerOffset[] " + "type, but got '" + + fv.second.type_name() + "'"); + } + } else if (fv.first == "endTimeCode") { + if (auto vf = fv.second.get_value()) { + metas->endTimeCode = double(vf.value()); + } else if (auto vd = fv.second.get_value()) { + metas->endTimeCode = vd.value(); + } else { + PUSH_ERROR_AND_RETURN( + "`endTimeCode` value must be double or float " + "type, but got '" + + fv.second.type_name() + "'"); + } + DCOUT("endTimeCode = " << metas->endTimeCode.get_value()); + } else if (fv.first == "framesPerSecond") { + if (auto vf = fv.second.get_value()) { + metas->framesPerSecond = double(vf.value()); + } else if (auto vd = fv.second.get_value()) { + metas->framesPerSecond = vd.value(); + } else { + PUSH_ERROR_AND_RETURN( + "`framesPerSecond` value must be double or float " + "type, but got '" + + fv.second.type_name() + "'"); + } + DCOUT("framesPerSecond = " << metas->framesPerSecond.get_value()); + } else if (fv.first == "autoPlay") { + if (auto vf = fv.second.get_value()) { + metas->autoPlay = vf.value(); + } else if (auto vs = fv.second.get_value()) { + // unregisteredvalue uses string type. + bool autoPlay{true}; + if (vs.value() == "true") { + autoPlay = true; + } else if (vs.value() == "false") { + autoPlay = false; + } else { + PUSH_ERROR_AND_RETURN( + "Unsupported value for `autoPlay`: " << vs.value()); + } + metas->autoPlay = autoPlay; + } else { + PUSH_ERROR_AND_RETURN( + "`autoPlay` value must be bool " + "type or string type, but got '" + + fv.second.type_name() + "'"); + } + DCOUT("autoPlay = " << metas->autoPlay.get_value()); + } else if (fv.first == "playbackMode") { + if (auto vf = fv.second.get_value()) { + if (vf.value().str() == "none") { + metas->playbackMode = StageMetas::PlaybackMode::PlaybackModeNone; + } else if (vf.value().str() == "loop") { + metas->playbackMode = StageMetas::PlaybackMode::PlaybackModeLoop; + } else { + PUSH_ERROR_AND_RETURN("Unsupported token value for `playbackMode`."); + } + } else if (auto vs = fv.second.get_value()) { + // unregisteredvalue uses string type. + if (vs.value() == "none") { + metas->playbackMode = StageMetas::PlaybackMode::PlaybackModeNone; + } else if (vs.value() == "loop") { + metas->playbackMode = StageMetas::PlaybackMode::PlaybackModeLoop; + } else { + PUSH_ERROR_AND_RETURN( + "Unsupported value for `playbackMode`: " << vs.value()); + } + } else { + PUSH_ERROR_AND_RETURN( + "`playbackMode` value must be token " + "type, but got '" + + fv.second.type_name() + "'"); + } + } else if ((fv.first == "defaultPrim")) { + auto v = fv.second.get_value(); + if (!v) { + PUSH_ERROR_AND_RETURN("`defaultPrim` must be `token` type."); + } + + metas->defaultPrim = v.value(); + DCOUT("defaultPrim = " << metas->defaultPrim.str()); + } else if (fv.first == "customLayerData") { + if (auto v = fv.second.get_value()) { + metas->customLayerData = v.value(); + } else { + PUSH_ERROR_AND_RETURN( + "customLayerData must be `dictionary` type, but got type `" + + fv.second.type_name()); + } + } else if (fv.first == "primChildren") { // only appears in USDC. + auto v = fv.second.get_value>(); + if (!v) { + PUSH_ERROR_AND_RETURN("Type must be `token[]` for `primChildren`, but got " + + fv.second.type_name()); + } + + metas->primChildren = v.value(); + } else if (fv.first == "documentation") { // 'doc' + auto v = fv.second.get_value(); + if (!v) { + PUSH_ERROR_AND_RETURN("Type must be `string` for `documentation`, but got " + + fv.second.type_name()); + } + value::StringData sdata; + sdata.value = v.value(); + sdata.is_triple_quoted = hasNewline(sdata.value); + metas->doc = sdata; + DCOUT("doc = " << metas->doc.value); + } else if (fv.first == "comment") { // 'comment' + auto v = fv.second.get_value(); + if (!v) { + PUSH_ERROR_AND_RETURN("Type must be `string` for `comment`, but got " + + fv.second.type_name()); + } + value::StringData sdata; + sdata.value = v.value(); + sdata.is_triple_quoted = hasNewline(sdata.value); + metas->comment = sdata; + DCOUT("comment = " << metas->comment.value); + } else { + PUSH_WARN("[StageMeta] TODO: " + fv.first); + } + } + + if (subLayers.size()) { + std::vector dst; + for (size_t i = 0; i < subLayers.size(); i++) { + SubLayer s; + s.assetPath = subLayers[i]; + dst.push_back(s); + } + + if (subLayers.size() == subLayerOffsets.size()) { + for (size_t i = 0; i < subLayerOffsets.size(); i++) { + dst[i].layerOffset = subLayerOffsets[i]; + } + } + + metas->subLayers = dst; + + } else if (subLayerOffsets.size()) { + PUSH_WARN("Corrupted subLayer info? `subLayers` Fileld not found."); + } + + return true; +} + +nonstd::optional USDCReader::Impl::ReconstructPrimFromTypeName( + const std::string &typeName, // TinyUSDZ's Prim type name + const std::string &primTypeName, // USD's Prim typeName + const std::string &prim_name, + const crate::CrateReader::Node &node, const Specifier spec, + const std::vector &primChildren, + const std::vector &properties, + const PathIndexToSpecIndexMap &psmap, const PrimMeta &meta, bool *is_unsupported_prim) { + + if (is_unsupported_prim) { + (*is_unsupported_prim) = false; // init with false + } + + +#define RECONSTRUCT_PRIM(__primty, __node_ty, __prim_name, __spec) \ + if (__node_ty == value::TypeTraits<__primty>::type_name()) { \ + __primty typed_prim; \ + if (!ReconstructPrim(__spec, node, psmap, &typed_prim)) { \ + PUSH_ERROR("Failed to reconstruct Prim " << __node_ty << " elementName: " << __prim_name); \ + return nonstd::nullopt; \ + } \ + typed_prim.meta = meta; \ + typed_prim.name = __prim_name; \ + typed_prim.spec = __spec; \ + typed_prim.propertyNames() = properties; \ + typed_prim.primChildrenNames() = primChildren; \ + value::Value primdata = typed_prim; \ + Prim prim(__prim_name, primdata); \ + prim.prim_type_name() = primTypeName; \ + /* also add primChildren to Prim */ \ + prim.metas().primChildren = primChildren; \ + return std::move(prim); \ + } else + + if (typeName == "Model" || typeName == "__AnyType__") { + // Code is mostly identical to RECONSTRUCT_PRIM. + // Difference is store primTypeName to Model class itself. + Model typed_prim; + if (!ReconstructPrim(spec, node, psmap, &typed_prim)) { + PUSH_ERROR("Failed to reconstruct Model"); + return nonstd::nullopt; + } + typed_prim.meta = meta; + typed_prim.name = prim_name; + if (typeName == "__AnyType__") { + typed_prim.prim_type_name = ""; + } else { + typed_prim.prim_type_name = primTypeName; + } + typed_prim.spec = spec; + typed_prim.propertyNames() = properties; + typed_prim.primChildrenNames() = primChildren; + value::Value primdata = typed_prim; + Prim prim(prim_name, primdata); + prim.prim_type_name() = primTypeName; + /* also add primChildren to Prim */ + prim.metas().primChildren = primChildren; \ + return std::move(prim); \ + } else + + RECONSTRUCT_PRIM(Xform, typeName, prim_name, spec) + RECONSTRUCT_PRIM(Model, typeName, prim_name, spec) + RECONSTRUCT_PRIM(Scope, typeName, prim_name, spec) + RECONSTRUCT_PRIM(GeomMesh, typeName, prim_name, spec) + RECONSTRUCT_PRIM(GeomPoints, typeName, prim_name, spec) + RECONSTRUCT_PRIM(GeomCylinder, typeName, prim_name, spec) + RECONSTRUCT_PRIM(GeomCube, typeName, prim_name, spec) + RECONSTRUCT_PRIM(GeomCone, typeName, prim_name, spec) + RECONSTRUCT_PRIM(GeomSphere, typeName, prim_name, spec) + RECONSTRUCT_PRIM(GeomCapsule, typeName, prim_name, spec) + RECONSTRUCT_PRIM(GeomBasisCurves, typeName, prim_name, spec) + RECONSTRUCT_PRIM(GeomNurbsCurves, typeName, prim_name, spec) + RECONSTRUCT_PRIM(PointInstancer, typeName, prim_name, spec) + RECONSTRUCT_PRIM(GeomCamera, typeName, prim_name, spec) + RECONSTRUCT_PRIM(GeomSubset, typeName, prim_name, spec) + RECONSTRUCT_PRIM(SphereLight, typeName, prim_name, spec) + RECONSTRUCT_PRIM(DomeLight, typeName, prim_name, spec) + RECONSTRUCT_PRIM(CylinderLight, typeName, prim_name, spec) + RECONSTRUCT_PRIM(DiskLight, typeName, prim_name, spec) + RECONSTRUCT_PRIM(DistantLight, typeName, prim_name, spec) + RECONSTRUCT_PRIM(SkelRoot, typeName, prim_name, spec) + RECONSTRUCT_PRIM(Skeleton, typeName, prim_name, spec) + RECONSTRUCT_PRIM(SkelAnimation, typeName, prim_name, spec) + RECONSTRUCT_PRIM(BlendShape, typeName, prim_name, spec) + RECONSTRUCT_PRIM(Shader, typeName, prim_name, spec) + RECONSTRUCT_PRIM(Material, typeName, prim_name, spec) { + PUSH_WARN("TODO or unsupported prim type: " << typeName); + if (is_unsupported_prim) { + (*is_unsupported_prim) = true; + } + return nonstd::nullopt; + } + +#undef RECONSTRUCT_PRIM +} + +/// +/// +/// Prim(Model) fieldSet example. +/// +/// +/// specTy = SpecTypePrim +/// +/// - specifier(specifier) : e.g. `def`, `over`, ... +/// - kind(token) : kind metadataum +/// - optional: typeName(token) : type name of Prim(e.g. `Xform`). No +/// typeName = `def "mynode"` +/// - primChildren(TokenVector): List of child prims. +/// - properties(TokenVector) : List of name of Prim properties. +/// +/// +bool USDCReader::Impl::ParsePrimSpec(const crate::FieldValuePairVector &fvs, + nonstd::optional &typeName, + nonstd::optional &specifier, + std::vector &primChildren, + std::vector &properties, + PrimMeta &primMeta) { + // Fields for Prim and Prim metas. + for (const auto &fv : fvs) { + if (fv.first == "typeName") { + if (auto pv = fv.second.as()) { + typeName = pv->str(); + DCOUT("typeName = " << typeName.value()); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`typeName` must be type `token`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "specifier") { + if (auto pv = fv.second.as()) { + specifier = (*pv); + DCOUT("specifier = " << to_string(specifier.value())); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`specifier` must be type `Specifier`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "properties") { + if (auto pv = fv.second.as>()) { + properties = (*pv); + DCOUT("properties = " << properties); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`properties` must be type `token[]`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "primChildren") { + // Crate only + if (auto pv = fv.second.as>()) { + primChildren = (*pv); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`primChildren` must be type `token[]`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "active") { + if (auto pv = fv.second.as()) { + primMeta.active = (*pv); + DCOUT("active = " << to_string(primMeta.active.value())); + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "`active` must be type `bool`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "hidden") { + if (auto pv = fv.second.as()) { + primMeta.hidden = (*pv); + DCOUT("hidden = " << to_string(primMeta.hidden.value())); + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "`hidden` must be type `bool`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "instanceable") { + if (auto pv = fv.second.as()) { + primMeta.instanceable = (*pv); + DCOUT("instanceable = " << to_string(primMeta.instanceable.value())); + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "`instanceable` must be type `bool`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "assetInfo") { + // CustomData(dict) + if (auto pv = fv.second.as()) { + primMeta.assetInfo = (*pv); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`assetInfo` must be type `dictionary`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "clips") { + // CustomData(dict) + if (auto pv = fv.second.as()) { + primMeta.clips = (*pv); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`clips` must be type `dictionary`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "kind") { + if (auto pv = fv.second.as()) { + + const value::token tok = (*pv); + if (tok.str() == "subcomponent") { + primMeta.kind = Kind::Subcomponent; + } else if (tok.str() == "component") { + primMeta.kind = Kind::Component; + } else if (tok.str() == "model") { + primMeta.kind = Kind::Model; + } else if (tok.str() == "group") { + primMeta.kind = Kind::Group; + } else if (tok.str() == "assembly") { + primMeta.kind = Kind::Assembly; + } else if (tok.str() == "sceneLibrary") { + // USDZ specific: https://developer.apple.com/documentation/arkit/usdz_schemas_for_ar/scenelibrary + primMeta.kind = Kind::SceneLibrary; + } else { + + primMeta.kind = Kind::UserDef; + primMeta._kind_str = tok.str(); + } + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "`kind` must be type `token`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "apiSchemas") { + if (auto pv = fv.second.as>()) { + auto listop = (*pv); + + auto ret = ToAPISchemas(listop); + if (!ret) { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "Failed to validate `apiSchemas`: " + ret.error()); + } else { + primMeta.apiSchemas = (*ret); + } + // DCOUT("apiSchemas = " << to_string(listop)); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`apiSchemas` must be type `ListOp[Token]`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "documentation") { + if (auto pv = fv.second.as()) { + value::StringData s; + s.value = (*pv); + s.is_triple_quoted = hasNewline(s.value); + primMeta.doc = s; + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`documentation` must be type `string`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "comment") { + if (auto pv = fv.second.as()) { + value::StringData s; + s.value = (*pv); + s.is_triple_quoted = hasNewline(s.value); + primMeta.comment = s; + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`comment` must be type `string`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "sdrMetadata") { + // CustomData(dict) + if (auto pv = fv.second.as()) { + // TODO: Check if all keys are string type. + primMeta.sdrMetadata = (*pv); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`sdrMetadata` must be type `dictionary`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "customData") { + // CustomData(dict) + if (auto pv = fv.second.as()) { + primMeta.customData = (*pv); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`customData` must be type `dictionary`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "variantSelection") { + if (auto pv = fv.second.as()) { + primMeta.variants = (*pv); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`variantSelection` must be type `variants`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "variantChildren") { + // Used internally + if (auto pv = fv.second.as>()) { + primMeta.variantChildren = (*pv); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`variantChildren` must be type `token[]`, but got type `" + << fv.second.type_name() << "`"); + } + + } else if (fv.first == "variantSetChildren") { + // Used internally + if (auto pv = fv.second.as>()) { + primMeta.variantSetChildren = (*pv); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`variantSetChildren` must be type `token[]`, but got type `" + << fv.second.type_name() << "`"); + } + + } else if (fv.first == "variantSetNames") { + // ListOp + if (auto pv = fv.second.as>()) { + const ListOp &p = *pv; + DCOUT("variantSetNames = " << to_string(p)); + + auto ps = DecodeListOp(p); + + if (ps.size() > 1) { + // This should not happen though. + PUSH_WARN( + "ListOp with multiple ListOpType is not supported for now. Use " + "the first one: " + + to_string(std::get<0>(ps[0]))); + } + + auto qual = std::get<0>(ps[0]); + auto items = std::get<1>(ps[0]); + auto listop = (*pv); + primMeta.variantSets = std::make_pair(qual, items); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, + "`variantSetNames` must be type `ListOp[String]`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "sceneName") { // USDZ extension + if (auto pv = fv.second.as()) { + primMeta.sceneName = (*pv); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`sceneName` must be type `string`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "displayName") { // USD supported since 23.xx? + if (auto pv = fv.second.as()) { + primMeta.displayName = (*pv); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`displayName` must be type `string`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "inherits") { // `inherits` composition + if (auto pvb = fv.second.as()) { + // make empty array + primMeta.inherits = + std::make_pair(ListEditQual::ResetToExplicit, std::vector()); + } else if (auto pv = fv.second.as>()) { + const ListOp &p = *pv; + DCOUT("inherits = " << to_string(p)); + + auto ps = DecodeListOp(p); + + if (ps.size() > 1) { + // This should not happen though. + PUSH_WARN( + "ListOp with multiple ListOpType is not supported for now. Use " + "the first one: " + + to_string(std::get<0>(ps[0]))); + } + + auto qual = std::get<0>(ps[0]); + auto items = std::get<1>(ps[0]); + primMeta.inherits = std::make_pair(qual, items); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`inherits` must be type `path` o `path[]`, but got type `" + << fv.second.type_name() << "`"); + } + + } else if (fv.first == "references") { // `references` composition + if (auto pvb = fv.second.as()) { + // make empty array + primMeta.references = std::make_pair(ListEditQual::ResetToExplicit, + std::vector()); + } else if (auto pv = fv.second.as>()) { + const ListOp &p = *pv; + DCOUT("references = " << to_string(p)); + + auto ps = DecodeListOp(p); + + if (ps.size() > 1) { + // This should not happen though. + PUSH_WARN( + "ListOp with multiple ListOpType is not supported for now. Use " + "the first one: " + + to_string(std::get<0>(ps[0]))); + } + + auto qual = std::get<0>(ps[0]); + auto items = std::get<1>(ps[0]); + auto listop = (*pv); + primMeta.references = std::make_pair(qual, items); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, + "`references` must be type `ListOp[Reference]`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "payload") { // `payload` composition + if (auto pvb = fv.second.as()) { + // make empty array + primMeta.payload = std::make_pair(ListEditQual::ResetToExplicit, + std::vector()); + } else if (auto pv = fv.second.as()) { + // payload can be non-listop + + std::vector pls; + pls.push_back(*pv); + primMeta.payload = std::make_pair(ListEditQual::ResetToExplicit, pls); + } else if (auto pvs = fv.second.as>()) { + const ListOp &p = *pvs; + DCOUT("payload = " << to_string(p)); + + auto ps = DecodeListOp(p); + + if (ps.size() > 1) { + // This should not happen though. + PUSH_WARN( + "ListOp with multiple ListOpType is not supported for now. Use " + "the first one: " + + to_string(std::get<0>(ps[0]))); + } + + auto qual = std::get<0>(ps[0]); + auto items = std::get<1>(ps[0]); + auto listop = (*pvs); + primMeta.payload = std::make_pair(qual, items); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, + "`payload` must be type `ListOp[Payload]`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "specializes") { // `specializes` composition + if (auto pv = fv.second.as>()) { + const ListOp &p = *pv; + DCOUT("specializes = " << to_string(p)); + + auto ps = DecodeListOp(p); + + if (ps.size() > 1) { + // This should not happen though. + PUSH_WARN( + "ListOp with multiple ListOpType is not supported for now. Use " + "the first one: " + + to_string(std::get<0>(ps[0]))); + } + + auto qual = std::get<0>(ps[0]); + auto items = std::get<1>(ps[0]); + auto listop = (*pv); + primMeta.specializes = std::make_pair(qual, items); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`specializes` must be type `ListOp[Path]`, but got type `" + << fv.second.type_name() << "`"); + } + } else if (fv.first == "inheritPaths") { // `specializes` composition + if (auto pv = fv.second.as>()) { + const ListOp &p = *pv; + DCOUT("inheritPaths = " << to_string(p)); + + auto ps = DecodeListOp(p); + + if (ps.size() > 1) { + // This should not happen though. + PUSH_WARN( + "ListOp with multiple ListOpType is not supported for now. Use " + "the first one: " + + to_string(std::get<0>(ps[0]))); + } + + auto qual = std::get<0>(ps[0]); + auto items = std::get<1>(ps[0]); + auto listop = (*pv); + primMeta.inheritPaths = std::make_pair(qual, items); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`inheritPaths` must be type `ListOp[Path]`, but got type `" + << fv.second.type_name() << "`"); + } + + } else { + // TODO: support int, int[], uint, uint[], int64, uint64, ... + // https://github.com/syoyo/tinyusdz/issues/106 + if (auto pv = fv.second.as()) { + // Assume unregistered Prim metadatum + primMeta.unregisteredMetas[fv.first] = (*pv); + } else if (auto ptv = fv.second.as()) { + // store value as string type. + primMeta.unregisteredMetas[fv.first] = quote((*ptv).str()); + } else { + DCOUT("PrimProp TODO: " << fv.first); + PUSH_WARN("PrimProp TODO: " << fv.first); + } + } + } + + return true; +} + +/// +/// +/// VariantSet fieldSet example. +/// +/// +/// specTy = SpecTypeVariantSet +/// +/// - variantChildren(token[]) +/// +/// +bool USDCReader::Impl::ParseVariantSetFields( + const crate::FieldValuePairVector &fvs, + std::vector &variantChildren) { + // Fields for Prim and Prim metas. + for (const auto &fv : fvs) { + if (fv.first == "variantChildren") { + if (auto pv = fv.second.as>()) { + variantChildren = (*pv); + DCOUT("variantChildren: " << variantChildren); + } else { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "`variantChildren` must be type `token[]`, but got type `" + << fv.second.type_name() << "`"); + } + } else { + DCOUT("VariantSet field TODO: " << fv.first); + PUSH_WARN("VariantSet field TODO: " << fv.first); + } + } + + return true; +} + +bool USDCReader::Impl::ReconstructPrimNode(int parent, int current, int level, + bool is_parent_variant, + const PathIndexToSpecIndexMap &psmap, + Stage *stage, + nonstd::optional *primOut) { + (void)level; + const crate::CrateReader::Node &node = _nodes[size_t(current)]; + + DCOUT(fmt::format("parent = {}, curent = {}, is_parent_variant = {}", parent, current, is_parent_variant)); + +#ifdef TINYUSDZ_LOCAL_DEBUG_PRINT + std::cout << pprint::Indent(uint32_t(level)) << "lv[" << level + << "] node_index[" << current << "] " << node.GetLocalPath() + << " ==\n"; + std::cout << pprint::Indent(uint32_t(level)) << " childs = ["; + for (size_t i = 0; i < node.GetChildren().size(); i++) { + std::cout << node.GetChildren()[i]; + if (i != (node.GetChildren().size() - 1)) { + std::cout << ", "; + } + } + std::cout << "] (is_parent_variant = " << is_parent_variant << ")\n"; +#endif + + if (!psmap.count(uint32_t(current))) { + // No specifier assigned to this node. + DCOUT("No specifier assigned to this node: " << current); + return true; // would be OK. + } + + uint32_t spec_index = psmap.at(uint32_t(current)); + if (spec_index >= _specs.size()) { + PUSH_ERROR("Invalid specifier id: " + std::to_string(spec_index) + + ". Must be in range [0, " + std::to_string(_specs.size()) + ")"); + return false; + } + + const crate::Spec &spec = _specs[spec_index]; + + DCOUT(pprint::Indent(uint32_t(level)) + << " specTy = " << to_string(spec.spec_type)); + DCOUT(pprint::Indent(uint32_t(level)) + << " fieldSetIndex = " << spec.fieldset_index.value); + + if ((spec.spec_type == SpecType::Attribute) || + (spec.spec_type == SpecType::Relationship)) { + if (_prim_table.count(parent)) { + // This node is a Properties node. These are processed in + // ReconstructPrim(), so nothing to do here. + return true; + } + } + + if (!_live_fieldsets.count(spec.fieldset_index)) { + PUSH_ERROR("FieldSet id: " + std::to_string(spec.fieldset_index.value) + + " must exist in live fieldsets."); + return false; + } + + const crate::FieldValuePairVector &fvs = + _live_fieldsets.at(spec.fieldset_index); + + if (fvs.size() > _config.kMaxFieldValuePairs) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Too much FieldValue pairs."); + } + +#if defined(TINYUSDZ_LOCAL_DEBUG_PRINT) + // DBG + for (auto &fv : fvs) { + DCOUT("parent[" << current << "] level [" << level << "] fv name " + << fv.first << "(type = " << fv.second.type_name() << ")"); + } +#endif + + // StageMeta = root only attributes. + // TODO: Unify reconstrction code with USDAReder? + if (current == 0) { + if (const auto &pv = GetElemPath(crate::Index(uint32_t(current)))) { + DCOUT("Root element path: " << pv.value().full_path_name()); + } else { + PUSH_ERROR_AND_RETURN("(Internal error). Root Element Path not found."); + } + + // Root layer(Stage) is PseudoRoot spec type. + if (spec.spec_type != SpecType::PseudoRoot) { + PUSH_ERROR_AND_RETURN( + "SpecTypePseudoRoot expected for root layer(Stage) element."); + } + + if (!ReconstrcutStageMeta(fvs, &stage->metas())) { + PUSH_ERROR_AND_RETURN("Failed to reconstruct StageMeta."); + } + + // TODO: Validate scene using `StageMetas::primChildren`. + + _prim_table.insert(current); + + return true; + } + + DCOUT("spec.type = " << to_string(spec.spec_type)); + switch (spec.spec_type) { + case SpecType::PseudoRoot: { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "SpecType PseudoRoot in a child node is not supported(yet)"); + } + case SpecType::Prim: { + nonstd::optional typeName; + nonstd::optional specifier; + std::vector primChildren; + std::vector properties; + + PrimMeta primMeta; + + DCOUT("== PrimFields begin ==> "); + + if (!ParsePrimSpec(fvs, typeName, specifier, primChildren, properties, primMeta)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to parse Prim fields."); + return false; + } + + DCOUT("<== PrimFields end ==="); + + Path elemPath; + + if (const auto &pv = GetElemPath(crate::Index(uint32_t(current)))) { + DCOUT(fmt::format("Element path: {}", pv.value().full_path_name())); + elemPath = pv.value(); + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "(Internal errror) Element path not found."); + } + + // Sanity check + if (specifier) { + if (specifier.value() == Specifier::Def) { + // ok + } else if (specifier.value() == Specifier::Class) { + // ok + } else if (specifier.value() == Specifier::Over) { + // ok + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid Specifier."); + } + } else { + // default `over` + specifier = Specifier::Over; + } + + std::string pTyName; + if (!typeName) { + //PUSH_WARN("Treat this node as Model(`typeName` field is missing)."); + pTyName = "Model"; + } else { + pTyName = typeName.value(); + } + + { + DCOUT("elemPath.prim_name = " << elemPath.prim_part()); + std::string prim_name = elemPath.prim_part(); + std::string primTypeName = typeName.has_value() ? typeName.value() : ""; + + // __AnyType__ + if (typeName.has_value() && typeName.value() == "__AnyType__") { + primTypeName = ""; + } + + // Validation check should be already done in crate-reader, so no + // further validation required. + if (!ValidatePrimElementName(prim_name)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid Prim name."); + } + + bool is_unsupported_prim{false}; + auto prim = ReconstructPrimFromTypeName(pTyName, primTypeName, prim_name, + node, specifier.value(), primChildren, properties, + psmap, primMeta, &is_unsupported_prim); + + if (prim) { + // Prim name + prim.value().element_path() = elemPath; + } else { + if (_config.allow_unknown_prims && is_unsupported_prim) { + // Try to reconsrtuct as Model + prim = ReconstructPrimFromTypeName("Model", primTypeName, prim_name, + node, specifier.value(), primChildren, properties, + psmap, primMeta); + if (prim) { + // Prim name + prim.value().element_path() = elemPath; + } else { + return false; + } + } else { + return false; + } + } + + if (primOut) { + (*primOut) = prim; + } + } + + DCOUT("add prim idx " << current); + if (_prim_table.count(current)) { + DCOUT("??? prim idx already set " << current); + } else { + _prim_table.insert(current); + } + + break; + } + case SpecType::VariantSet: { + // Assume parent(Prim) already exists(parsed) + // TODO: Confirm Crate format allow defining Prim after VariantSet + // serialization. + if (!_prim_table.count(parent)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "Parent Prim for this VariantSet not found."); + } + + DCOUT( + fmt::format("[{}] is a VariantSet node(parent = {}). prim_idx? = {}", + current, parent, _prim_table.count(current))); + + Path elemPath; + + if (const auto &pv = GetElemPath(crate::Index(uint32_t(current)))) { + elemPath = pv.value(); + + DCOUT(fmt::format("Element path: {}", dump_path(elemPath))); + + // Ensure ElementPath is variant + if (!tokenize_variantElement(elemPath.full_path_name())) { + PUSH_ERROR_AND_RETURN_TAG( + kTag, fmt::format("Invalid Variant ElementPath '{}'.", elemPath)); + } + + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "(Internal errror) Element path not found."); + } + + std::vector variantChildren; + + // Only contains `variantChildren` field with type `token[]` + + DCOUT("== VariantSetFields begin ==> "); + + if (!ParseVariantSetFields(fvs, variantChildren)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to parse VariantSet fields."); + return false; + } + + DCOUT("<== VariantSetFields end === "); + + // Add variantChildren to prim node. + // TODO: elemPath + if (!AddVariantChildrenToPrimNode(parent, variantChildren)) { + return false; + } + + break; + } + case SpecType::Variant: { + // Since the Prim this Variant node belongs to is not yet reconstructed + // during the Prim tree traversal, We manage variant node separately + + DCOUT(fmt::format("[{}] is a Variant node(parent = {}). prim_idx? = {}", + current, parent, _prim_table.count(current))); + + nonstd::optional typeName; + nonstd::optional specifier; + std::vector primChildren; + std::vector properties; + + PrimMeta primMeta; + + DCOUT("== VariantFields begin ==> "); + + if (!ParsePrimSpec(fvs, typeName, specifier, primChildren, properties, primMeta)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "Failed to parse Prim fields under Variant."); + return false; + } + + DCOUT("<== VariantFields end === "); + + Path elemPath; + if (const auto &pv = GetElemPath(crate::Index(uint32_t(current)))) { + elemPath = pv.value(); + DCOUT(fmt::format("Element path: {}", elemPath.full_path_name())); + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "(Internal errror) Element path not found."); + } + + // Sanity check + if (specifier) { + if (specifier.value() == Specifier::Def) { + // ok + } else if (specifier.value() == Specifier::Class) { + // ok + } else if (specifier.value() == Specifier::Over) { + // ok + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid Specifier."); + } + } else { + // Seems Variant is only composed of Properties. + // Create pseudo `def` Prim + specifier = Specifier::Def; + } + + std::string pTyName; // TinyUSDZ' prim typename + if (!typeName) { + //PUSH_WARN("Treat this node as Model(where `typeName` is missing)."); + pTyName = "Model"; + } else { + pTyName = typeName.value(); + } + + nonstd::optional variantPrim; + { + std::string prim_name = elemPath.prim_part(); + DCOUT("elemPath = " << dump_path(elemPath)); + DCOUT("prim_name = " << prim_name); + + std::string primTypeName = typeName.has_value() ? typeName.value() : ""; + // __AnyType__ + if (typeName.has_value() && typeName.value() == "__AnyType__") { + primTypeName = ""; + } + + // Something like '{shapeVariant=Capsule}' + + std::array variantPair; + if (!tokenize_variantElement(prim_name, &variantPair)) { + PUSH_ERROR_AND_RETURN_TAG( + kTag, fmt::format("Invalid Variant ElementPath '{}'.", elemPath)); + } + + std::string variantSetName = variantPair[0]; + std::string variantPrimName = variantPair[1]; + + if (!ValidatePrimElementName(variantPrimName)) { + PUSH_ERROR_AND_RETURN_TAG( + kTag, fmt::format("Invalid Prim name in Variant: `{}`", + variantPrimName)); + } + + bool is_unsupported_prim{false}; + variantPrim = ReconstructPrimFromTypeName( + pTyName, primTypeName, variantPrimName, node, specifier.value(), primChildren, properties, + psmap, primMeta, &is_unsupported_prim); + + if (variantPrim) { + // Prim name + variantPrim.value().element_path() = + elemPath; // FIXME: Use variantPrimName? + + // Prim Specifier + variantPrim.value().specifier() = specifier.value(); + + // Store variantPrim to temporary buffer. + DCOUT(fmt::format("parent {} add prim idx {} as variant: ", parent, current)); + if (_variantPrims.count(current)) { + DCOUT("??? prim idx already set " << current); + } else { + _variantPrims.emplace(current, variantPrim.value()); + _variantPrimChildren[parent].push_back(current); + } + } else { + if (_config.allow_unknown_prims && is_unsupported_prim) { + // Try to reconstruct as Model + variantPrim = ReconstructPrimFromTypeName( + "Model", primTypeName, variantPrimName, node, specifier.value(), primChildren, properties, + psmap, primMeta); + + if (variantPrim) { + // Prim name + variantPrim.value().element_path() = + elemPath; // FIXME: Use variantPrimName? + + // Prim Specifier + variantPrim.value().specifier() = specifier.value(); + + // Store variantPrim to temporary buffer. + DCOUT(fmt::format("parent {} add prim idx {} as variant: ", parent, current)); + if (_variantPrims.count(current)) { + DCOUT("??? prim idx already set " << current); + } else { + _variantPrims.emplace(current, variantPrim.value()); + _variantPrimChildren[parent].push_back(current); + } + } else { + return false; + } + } else { + return false; + } + } + } + + break; + } + case SpecType::Attribute: { + if (is_parent_variant) { + nonstd::optional path = GetPath(spec.path_index); + + if (!path) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid PathIndex."); + } + + Property prop; + if (!ParseProperty(spec.spec_type, fvs, &prop)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, + fmt::format("Failed to parse Attribut: {}.", + path.value().prop_part())); + } + + // Parent Prim is not yet reconstructed, so store info to temporary + // buffer _variantAttributeNodes. + _variantProps[current] = {path.value(), prop}; + _variantPropChildren[parent].push_back(current); + + DCOUT( + fmt::format("parent {} current [{}] Parsed Attribute {} under Variant. PathIndex {}", + parent, current, path.value().prop_part(), spec.path_index)); + + } else { + // Maybe parent is Class/Over, or inherited + PUSH_WARN( + "TODO: SpecTypeAttribute(in conjunction with Class/Over specifier, " + "or inherited?)"); + } + break; + } + case SpecType::Connection: + case SpecType::Relationship: + case SpecType::RelationshipTarget: { + PUSH_ERROR_AND_RETURN_TAG( + kTag, fmt::format("TODO: Unsupported/Unimplemented SpecType: {}.", + to_string(spec.spec_type))); + break; + } + case SpecType::Expression: + case SpecType::Mapper: + case SpecType::MapperArg: { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Unsupported SpecType: {}.", + to_string(spec.spec_type))); + break; + } + case SpecType::Unknown: + case SpecType::Invalid: { + PUSH_ERROR_AND_RETURN_TAG(kTag, "[InternalError] Invalid SpecType."); + break; + } + } + + return true; +} + +bool USDCReader::Impl::ReconstructPrimSpecNode(int parent, int current, int level, + bool is_parent_variant, + const PathIndexToSpecIndexMap &psmap, + Layer *layer, + nonstd::optional *primOut) { + (void)level; + const crate::CrateReader::Node &node = _nodes[size_t(current)]; + +#ifdef TINYUSDZ_LOCAL_DEBUG_PRINT + std::cout << pprint::Indent(uint32_t(level)) << "lv[" << level + << "] node_index[" << current << "] " << node.GetLocalPath() + << " ==\n"; + std::cout << pprint::Indent(uint32_t(level)) << " childs = ["; + for (size_t i = 0; i < node.GetChildren().size(); i++) { + std::cout << node.GetChildren()[i]; + if (i != (node.GetChildren().size() - 1)) { + std::cout << ", "; + } + } + std::cout << "] (is_parent_variant = " << is_parent_variant << ")\n"; +#endif + + if (!psmap.count(uint32_t(current))) { + // No specifier assigned to this node. + DCOUT("No specifier assigned to this node: " << current); + return true; // would be OK. + } + + uint32_t spec_index = psmap.at(uint32_t(current)); + if (spec_index >= _specs.size()) { + PUSH_ERROR("Invalid specifier id: " + std::to_string(spec_index) + + ". Must be in range [0, " + std::to_string(_specs.size()) + ")"); + return false; + } + + const crate::Spec &spec = _specs[spec_index]; + + DCOUT(pprint::Indent(uint32_t(level)) + << " specTy = " << to_string(spec.spec_type)); + DCOUT(pprint::Indent(uint32_t(level)) + << " fieldSetIndex = " << spec.fieldset_index.value); + + if ((spec.spec_type == SpecType::Attribute) || + (spec.spec_type == SpecType::Relationship)) { + if (_prim_table.count(parent)) { + // This node is a Properties node. These are processed in + // ReconstructPrim(), so nothing to do here. + return true; + } + } + + if (!_live_fieldsets.count(spec.fieldset_index)) { + PUSH_ERROR("FieldSet id: " + std::to_string(spec.fieldset_index.value) + + " must exist in live fieldsets."); + return false; + } + + const crate::FieldValuePairVector &fvs = + _live_fieldsets.at(spec.fieldset_index); + + if (fvs.size() > _config.kMaxFieldValuePairs) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Too much FieldValue pairs."); + } + +#if defined(TINYUSDZ_LOCAL_DEBUG_PRINT) + // DBG + for (auto &fv : fvs) { + DCOUT("parent[" << current << "] level [" << level << "] fv name " + << fv.first << "(type = " << fv.second.type_name() << ")"); + } +#endif + + // StageMeta = root only attributes. + // TODO: Unify reconstrction code with USDAReder? + if (current == 0) { + if (const auto &pv = GetElemPath(crate::Index(uint32_t(current)))) { + DCOUT("Root element path: " << pv.value().full_path_name()); + } else { + PUSH_ERROR_AND_RETURN("(Internal error). Root Element Path not found."); + } + + // Root layer(Stage) is PseudoRoot spec type. + if (spec.spec_type != SpecType::PseudoRoot) { + PUSH_ERROR_AND_RETURN( + "SpecTypePseudoRoot expected for root layer(Stage) element."); + } + + if (!ReconstrcutStageMeta(fvs, &layer->metas())) { + PUSH_ERROR_AND_RETURN("Failed to reconstruct StageMeta."); + } + + // TODO: Validate scene using `StageMetas::primChildren`. + + _prim_table.insert(current); + + return true; + } + + switch (spec.spec_type) { + case SpecType::PseudoRoot: { + PUSH_ERROR_AND_RETURN_TAG( + kTag, "SpecType PseudoRoot in a child node is not supported(yet)"); + } + case SpecType::Prim: { + nonstd::optional typeName; + nonstd::optional specifier; + std::vector primChildren; + std::vector properties; + + PrimMeta primMeta; + + DCOUT("== PrimFields begin ==> "); + + if (!ParsePrimSpec(fvs, typeName, specifier, primChildren, properties, primMeta)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to parse Prim fields."); + return false; + } + + DCOUT("<== PrimFields end ==="); + + Path elemPath; + + if (const auto &pv = GetElemPath(crate::Index(uint32_t(current)))) { + DCOUT(fmt::format("Element path: {}", pv.value().full_path_name())); + elemPath = pv.value(); + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "(Internal errror) Element path not found."); + } + + // Sanity check + if (specifier) { + if (specifier.value() == Specifier::Def) { + // ok + } else if (specifier.value() == Specifier::Class) { + // ok + } else if (specifier.value() == Specifier::Over) { + // ok + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid Specifier."); + } + } else { + // Default = Over Prim. + specifier = Specifier::Over; + } + + std::string pTyName; + if (!typeName) { + //PUSH_WARN("Treat this node as Model(`typeName` field is missing)."); + pTyName = "Model"; + } else { + pTyName = typeName.value(); + } + + { + DCOUT("elemPath.prim_name = " << elemPath.prim_part()); + std::string prim_name = elemPath.prim_part(); + std::string primTypeName = typeName.has_value() ? typeName.value() : ""; + // __AnyType__ + if (typeName.has_value() && typeName.value() == "__AnyType__") { + primTypeName = ""; + } + + // Validation check should be already done in crate-reader, so no + // further validation required. + if (!ValidatePrimElementName(prim_name)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid Prim name."); + } + + PrimSpec primspec; + +#if 0 + bool is_unsupported_prim{false}; + auto prim = ReconstructPrimFromTypeName(pTyName, primTypeName, prim_name, + node, specifier.value(), primChildren, properties, + psmap, primMeta, &is_unsupported_prim); + + if (prim) { + // Prim name + prim.value().element_path() = elemPath; + } else { + if (_config.allow_unknown_prims && is_unsupported_prim) { + // Try to reconsrtuct as Model + prim = ReconstructPrimFromTypeName("Model", primTypeName, prim_name, + node, specifier.value(), primChildren, properties, + psmap, primMeta); + if (prim) { + // Prim name + prim.value().element_path() = elemPath; + } else { + return false; + } + } else { + return false; + } + } +#else + primspec.typeName() = primTypeName; + primspec.name() = prim_name; + + prim::PropertyMap props; + if (!BuildPropertyMap(node.GetChildren(), psmap, &props)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to build PropertyMap."); + } + primspec.props() = props; + primspec.metas() = primMeta; + // TODO: primChildren, properties + + if (primOut) { + (*primOut) = primspec; + } +#endif + } + + DCOUT("add prim idx " << current); + if (_prim_table.count(current)) { + DCOUT("??? prim idx already set " << current); + } else { + _prim_table.insert(current); + } + + break; + } + case SpecType::VariantSet: { + // Assume parent(Prim) already exists(parsed) + // TODO: Confirm Crate format allow defining Prim after VariantSet + // serialization. + if (!_prim_table.count(parent)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "Parent Prim for this VariantSet not found."); + } + + DCOUT( + fmt::format("[{}] is a Variantset node(parent = {}). prim_idx? = {}", + current, parent, _prim_table.count(current))); + + Path elemPath; + + if (const auto &pv = GetElemPath(crate::Index(uint32_t(current)))) { + elemPath = pv.value(); + + DCOUT(fmt::format("Element path: {}", dump_path(elemPath))); + + // Ensure ElementPath is variant + if (!tokenize_variantElement(elemPath.full_path_name())) { + PUSH_ERROR_AND_RETURN_TAG( + kTag, fmt::format("Invalid Variant ElementPath '{}'.", elemPath)); + } + + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "(Internal errror) Element path not found."); + } + + std::vector variantChildren; + + // Only contains `variantChildren` field with type `token[]` + + DCOUT("== VariantSetFields begin ==> "); + + if (!ParseVariantSetFields(fvs, variantChildren)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to parse VariantSet fields."); + return false; + } + + DCOUT("<== VariantSetFields end === "); + + // Add variantChildren to prim node. + // TODO: elemPath + if (!AddVariantChildrenToPrimNode(parent, variantChildren)) { + return false; + } + + break; + } + case SpecType::Variant: { + // Since the Prim this Variant node belongs to is not yet reconstructed + // during the Prim tree traversal, We manage variant node separately + + DCOUT(fmt::format("[{}] is a Variant node(parent = {}). prim_idx? = {}", + current, parent, _prim_table.count(current))); + + nonstd::optional typeName; + nonstd::optional specifier; + std::vector primChildren; + std::vector properties; + + PrimMeta primMeta; + + DCOUT("== VariantFields begin ==> "); + + if (!ParsePrimSpec(fvs, typeName, specifier, primChildren, properties, primMeta)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "Failed to parse Prim fields under Variant."); + return false; + } + + DCOUT("<== VariantFields end === "); + + Path elemPath; + if (const auto &pv = GetElemPath(crate::Index(uint32_t(current)))) { + elemPath = pv.value(); + DCOUT(fmt::format("Element path: {}", elemPath.full_path_name())); + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, + "(Internal errror) Element path not found."); + } + + // Sanity check + if (specifier) { + if (specifier.value() == Specifier::Def) { + // ok + } else if (specifier.value() == Specifier::Class) { + // ok + } else if (specifier.value() == Specifier::Over) { + // ok + } else { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid Specifier."); + } + } else { + // Seems Variant is only composed of Properties. + // Create pseudo `def` Prim + // FIXME: default is `Over`? + specifier = Specifier::Def; + } + + std::string pTyName; // TinyUSDZ' prim typename + if (!typeName) { + //PUSH_WARN("Treat this node as Model(where `typeName` is missing)."); + pTyName = "Model"; + } else { + pTyName = typeName.value(); + } + + { + std::string prim_name = elemPath.prim_part(); + DCOUT("elemPath = " << dump_path(elemPath)); + DCOUT("prim_name = " << prim_name); + + std::string primTypeName = typeName.has_value() ? typeName.value() : ""; + // __AnyType__ + if (typeName.has_value() && typeName.value() == "__AnyType__") { + primTypeName = ""; + } + + // Something like '{shapeVariant=Capsule}' + + std::array variantPair; + if (!tokenize_variantElement(prim_name, &variantPair)) { + PUSH_ERROR_AND_RETURN_TAG( + kTag, fmt::format("Invalid Variant ElementPath '{}'.", elemPath)); + } + + std::string variantSetName = variantPair[0]; + std::string variantPrimName = variantPair[1]; + + if (!ValidatePrimElementName(variantPrimName)) { + PUSH_ERROR_AND_RETURN_TAG( + kTag, fmt::format("Invalid Prim name in Variant: `{}`", + variantPrimName)); + } + +#if 0 + nonstd::optional variantPrimSpec; + bool is_unsupported_prim{false}; + variantPrim = ReconstructPrimFromTypeName( + pTyName, primTypeName, variantPrimName, node, specifier.value(), primChildren, properties, + psmap, primMeta, &is_unsupported_prim); + + if (variantPrim) { + // Prim name + variantPrim.value().element_path() = + elemPath; // FIXME: Use variantPrimName? + + // Prim Specifier + variantPrim.value().specifier() = specifier.value(); + + // Store variantPrim to temporary buffer. + DCOUT(fmt::format("parent {} add prim idx {} as variant: ", parent, current)); + if (_variantPrims.count(current)) { + DCOUT("??? prim idx already set " << current); + } else { + _variantPrims[current] = variantPrim.value(); + _variantPrimChildren[parent].push_back(current); + } + } else { + if (_config.allow_unknown_prims && is_unsupported_prim) { + // Try to reconstruct as Model + variantPrim = ReconstructPrimFromTypeName( + "Model", primTypeName, variantPrimName, node, specifier.value(), primChildren, properties, + psmap, primMeta); + + if (variantPrim) { + // Prim name + variantPrim.value().element_path() = + elemPath; // FIXME: Use variantPrimName? + + // Prim Specifier + variantPrim.value().specifier() = specifier.value(); + + // Store variantPrim to temporary buffer. + DCOUT(fmt::format("parent {} add prim idx {} as variant: ", parent, current)); + if (_variantPrims.count(current)) { + DCOUT("??? prim idx already set " << current); + } else { + _variantPrims[current] = variantPrim.value(); + _variantPrimChildren[parent].push_back(current); + } + } else { + return false; + } + } else { + return false; + } + } +#else + PrimSpec variantPrimSpec; + variantPrimSpec.typeName() = primTypeName; + variantPrimSpec.name() = prim_name; + + prim::PropertyMap props; + if (!BuildPropertyMap(node.GetChildren(), psmap, &props)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Failed to build PropertyMap."); + } + variantPrimSpec.props() = props; + variantPrimSpec.metas() = primMeta; + + // Store variantPrimSpec to temporary buffer. + DCOUT(fmt::format("parent {} add primspec idx {} as variant: ", parent, current)); + if (_variantPrimSpecs.count(current)) { + DCOUT("??? prim idx already set " << current); + } else { + _variantPrimSpecs[current] = variantPrimSpec; + _variantPrimChildren[parent].push_back(current); + } + +#endif + } + + break; + } + case SpecType::Attribute: { + if (is_parent_variant) { + nonstd::optional path = GetPath(spec.path_index); + + if (!path) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Invalid PathIndex."); + } + + Property prop; + if (!ParseProperty(spec.spec_type, fvs, &prop)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, + fmt::format("Failed to parse Attribut: {}.", + path.value().prop_part())); + } + + // Parent Prim is not yet reconstructed, so store info to temporary + // buffer _variantAttributeNodes. + _variantProps[current] = {path.value(), prop}; + _variantPropChildren[parent].push_back(current); + + DCOUT( + fmt::format("parent {} current [{}] Parsed Attribute {} under Variant. PathIndex {}", + parent, current, path.value().prop_part(), spec.path_index)); + + } else { + // Maybe parent is Class/Over, or inherited + PUSH_WARN( + "TODO: SpecTypeAttribute(in conjunction with Class/Over specifier, " + "or inherited?)"); + } + break; + } + case SpecType::Connection: + case SpecType::Relationship: + case SpecType::RelationshipTarget: { + PUSH_ERROR_AND_RETURN_TAG( + kTag, fmt::format("TODO: Unsupported/Unimplemented SpecType: {}.", + to_string(spec.spec_type))); + break; + } + case SpecType::Expression: + case SpecType::Mapper: + case SpecType::MapperArg: { + PUSH_ERROR_AND_RETURN_TAG(kTag, fmt::format("Unsupported SpecType: {}.", + to_string(spec.spec_type))); + break; + } + case SpecType::Unknown: + case SpecType::Invalid: { + PUSH_ERROR_AND_RETURN_TAG(kTag, "[InternalError] Invalid SpecType."); + break; + } + } + + return true; +} + +// +// TODO: rewrite code in bottom-up manner +// +bool USDCReader::Impl::ReconstructPrimRecursively( + int parent, int current, Prim *parentPrim, int level, + const PathIndexToSpecIndexMap &psmap, Stage *stage) { + if (level > int32_t(_config.kMaxPrimNestLevel)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "Prim hierarchy is too deep."); + } + + DCOUT("ReconstructPrimRecursively: parent = " + << std::to_string(parent) << ", current = " << current + << ", level = " << std::to_string(level)); + + if ((current < 0) || (current >= int(_nodes.size()))) { + PUSH_ERROR("Invalid current node id: " + std::to_string(current) + + ". Must be in range [0, " + std::to_string(_nodes.size()) + ")"); + return false; + } + + // + // TODO: Use bottom-up reconstruction(traverse child first) + // + + // null : parent node is Property or other Spec type. + // non-null : parent node is Prim + Prim *currPrimPtr = nullptr; + nonstd::optional prim; + + bool is_parent_variant = _variantPrims.count(parent); + + if (!ReconstructPrimNode(parent, current, level, is_parent_variant, psmap, + stage, &prim)) { + return false; + } + + if (prim) { + currPrimPtr = &(prim.value()); + } + + // Traverse children + { + const crate::CrateReader::Node &node = _nodes[size_t(current)]; + DCOUT("node.Children.size = " << node.GetChildren().size()); + for (size_t i = 0; i < node.GetChildren().size(); i++) { + DCOUT("Reconstuct Prim children: " << i << " / " + << node.GetChildren().size()); + if (!ReconstructPrimRecursively(current, int(node.GetChildren()[i]), + currPrimPtr, level + 1, psmap, stage)) { + return false; + } + DCOUT("DONE Reconstuct Prim children: " << i << " / " + << node.GetChildren().size()); + } + } + + // + // Reonstruct variant + // + DCOUT(fmt::format("parent {}, current {}", parent, current)); + + DCOUT(fmt::format(" has variant properties {}, has variant children {}", + _variantPropChildren.count(current), + _variantPrimChildren.count(current))); + + if (_variantPropChildren.count(current)) { + + // - parentPrim + // - variantPrim(SpecTypeVariant) <- current + // - variant property(SpecTypeAttribute) + + // + // `current` must be VariantPrim and `parentPrim` should exist + // + if (!_variantPrims.count(current)) { + PUSH_ERROR_AND_RETURN("Internal error: variant attribute is not a child of VariantPrim."); + } + + if (!parentPrim) { + PUSH_ERROR_AND_RETURN("Internal error: parentPrim should exist."); + } + + const Prim &variantPrim = _variantPrims.at(current); + + DCOUT("variant prim name: " << variantPrim.element_name()); + + + // element_name must be variant: "{variant=value}" + if (!is_variantElementName(variantPrim.element_name())) { + PUSH_ERROR_AND_RETURN("Corrupted Crate. VariantAttribute is not the child of VariantPrim."); + } + + std::array toks; + if (!tokenize_variantElement(variantPrim.element_name(), &toks)) { + PUSH_ERROR_AND_RETURN("Invalid variant element_name."); + } + + std::string variantSetName = toks[0]; + std::string variantName = toks[1]; + + Variant variant; + + for (const auto &item : _variantPropChildren.at(current)) { + // item should exist in _variantProps. + if (!_variantProps.count(item)) { + PUSH_ERROR_AND_RETURN("Internal error: variant Property not found."); + } + const std::pair &pp = _variantProps.at(item); + + std::string prop_name = std::get<0>(pp).prop_part(); + DCOUT(fmt::format(" node_index = {}, prop name {}", item, prop_name)); + + variant.properties()[prop_name] = std::get<1>(pp); + } + + VariantSet &vs = parentPrim->variantSets()[variantSetName]; + + if (vs.name.empty()) { + vs.name = variantSetName; + } + vs.variantSet[variantName] = variant; + + } + + if (_variantPrimChildren.count(current)) { + + // - currentPrim <- current + // - variant Prim children + + if (!prim) { + PUSH_ERROR_AND_RETURN("Internal error: must be Prim."); + } + + DCOUT(fmt::format("{} has variant Prim ", prim->element_name())); + + + for (const auto &item : _variantPrimChildren.at(current)) { + + if (!_variantPrims.count(item)) { + PUSH_ERROR_AND_RETURN("Internal error: variant Prim children not found."); + } + + const Prim &vp = _variantPrims.at(item); + + DCOUT(fmt::format(" variantPrim name {}", vp.element_name())); + + // element_name must be variant: "{variant=value}" + if (!is_variantElementName(vp.element_name())) { + PUSH_ERROR_AND_RETURN("Corrupted Crate. Variant Prim has invalid element_name."); + } + + std::array toks; + if (!tokenize_variantElement(vp.element_name(), &toks)) { + PUSH_ERROR_AND_RETURN("Invalid variant element_name."); + } + + std::string variantSetName = toks[0]; + std::string variantName = toks[1]; + + VariantSet &vs = prim->variantSets()[variantSetName]; + + if (vs.name.empty()) { + vs.name = variantSetName; + } + vs.variantSet[variantName].metas() = vp.metas(); + DCOUT("# of primChildren = " << vp.children().size()); + vs.variantSet[variantName].primChildren() = std::move(vp.children()); + + } + } + + if (parent == 0) { // root prim + if (prim) { + stage->root_prims().emplace_back(std::move(prim.value())); + } + } else { + if (_variantPrims.count(parent)) { + // Add to variantPrim + DCOUT("parent is variantPrim: " << parent); + if (!prim) { + // FIXME: Validate current should be Prim. + PUSH_WARN("parent is variantPrim, but current is not Prim."); + } else { + DCOUT("Adding prim to child..."); + Prim &vp = _variantPrims.at(parent); + vp.children().emplace_back(std::move(prim.value())); + } + } else if (prim && parentPrim) { + // Add to parent prim. + parentPrim->children().emplace_back(std::move(prim.value())); + } + } + + return true; +} + +bool USDCReader::Impl::ReconstructStage(Stage *stage) { + + // format test + DCOUT(fmt::format("# of Paths = {}", crate_reader->NumPaths())); + + if (crate_reader->NumNodes() == 0) { + PUSH_WARN("Empty scene."); + return true; + } + + // TODO: Directly access data in crate_reader. + _nodes = crate_reader->GetNodes(); + _specs = crate_reader->GetSpecs(); + _fields = crate_reader->GetFields(); + _fieldset_indices = crate_reader->GetFieldsetIndices(); + _paths = crate_reader->GetPaths(); + _elemPaths = crate_reader->GetElemPaths(); + _live_fieldsets = crate_reader->GetLiveFieldSets(); + + PathIndexToSpecIndexMap + path_index_to_spec_index_map; // path_index -> spec_index + + { + for (size_t i = 0; i < _specs.size(); i++) { + if (_specs[i].path_index.value == ~0u) { + continue; + } + + // path_index should be unique. + if (path_index_to_spec_index_map.count(_specs[i].path_index.value) != 0) { + PUSH_ERROR_AND_RETURN("Multiple PathIndex found in Crate data."); + } + + DCOUT(fmt::format("path index[{}] -> spec index [{}]", + _specs[i].path_index.value, uint32_t(i))); + path_index_to_spec_index_map[_specs[i].path_index.value] = uint32_t(i); + } + } + + stage->root_prims().clear(); + + int root_node_id = 0; + bool ret = ReconstructPrimRecursively(/* no further root for root_node */ -1, + root_node_id, /* root Prim */ nullptr, + /* level */ 0, + path_index_to_spec_index_map, stage); + + if (!ret) { + PUSH_ERROR_AND_RETURN("Failed to reconstruct Stage(Prim hierarchy)"); + } + + stage->compute_absolute_prim_path_and_assign_prim_id(); + + return true; +} + +bool USDCReader::Impl::ReconstructPrimSpecRecursively( + int parent, int current, PrimSpec *parentPrimSpec, int level, + const PathIndexToSpecIndexMap &psmap, Layer *layer) { + if (level > int32_t(_config.kMaxPrimNestLevel)) { + PUSH_ERROR_AND_RETURN_TAG(kTag, "PrimSpec hierarchy is too deep."); + } + + DCOUT("ReconstructPrimRecursively: parent = " + << std::to_string(parent) << ", current = " << current + << ", level = " << std::to_string(level)); + + if ((current < 0) || (current >= int(_nodes.size()))) { + PUSH_ERROR("Invalid current node id: " + std::to_string(current) + + ". Must be in range [0, " + std::to_string(_nodes.size()) + ")"); + return false; + } + + // TODO: Refactor + + // null : parent node is Property or other Spec type. + // non-null : parent node is PrimSpec + PrimSpec *currPrimSpecPtr = nullptr; + nonstd::optional primspec; + + // Assume parent node is already processed. + bool is_parent_variant = _variantPrims.count(parent); + + if (!ReconstructPrimSpecNode(parent, current, level, is_parent_variant, psmap, + layer, &primspec)) { + return false; + } + + if (primspec) { + currPrimSpecPtr = &(primspec.value()); + } + + { + const crate::CrateReader::Node &node = _nodes[size_t(current)]; + DCOUT("node.Children.size = " << node.GetChildren().size()); + for (size_t i = 0; i < node.GetChildren().size(); i++) { + DCOUT("Reconstuct Prim children: " << i << " / " + << node.GetChildren().size()); + if (!ReconstructPrimSpecRecursively(current, int(node.GetChildren()[i]), + currPrimSpecPtr, level + 1, psmap, layer)) { + return false; + } + DCOUT("DONE Reconstuct PrimSpec children: " << i << " / " + << node.GetChildren().size()); + } + } + + // + // Reonstruct variant + // + DCOUT(fmt::format("parent {}, current {}", parent, current)); + + DCOUT(fmt::format(" has variant properties {}, has variant children {}", + _variantPropChildren.count(current), + _variantPrimChildren.count(current))); + + if (_variantPropChildren.count(current)) { + + // - parentPrim + // - variantPrim(SpecTypeVariant) <- current + // - variant property(SpecTypeAttribute) + + // + // `current` must be VariantPrim and `parentPrim` should exist + // + if (!_variantPrims.count(current)) { + PUSH_ERROR_AND_RETURN("Internal error: variant attribute is not a child of VariantPrim."); + } + + if (!parentPrimSpec) { + PUSH_ERROR_AND_RETURN("Internal error: parentPrimSpec should exist."); + } + + const Prim &variantPrim = _variantPrims.at(current); + + DCOUT("variant prim name: " << variantPrim.element_name()); + + + // element_name must be variant: "{variant=value}" + if (!is_variantElementName(variantPrim.element_name())) { + PUSH_ERROR_AND_RETURN("Corrupted Crate. VariantAttribute is not the child of VariantPrim."); + } + + std::array toks; + if (!tokenize_variantElement(variantPrim.element_name(), &toks)) { + PUSH_ERROR_AND_RETURN("Invalid variant element_name."); + } + + std::string variantSetName = toks[0]; + std::string variantName = toks[1]; + + PrimSpec variant; + + for (const auto &item : _variantPropChildren.at(current)) { + // item should exist in _variantProps. + if (!_variantProps.count(item)) { + PUSH_ERROR_AND_RETURN("Internal error: variant Property not found."); + } + const std::pair &pp = _variantProps.at(item); + + std::string prop_name = std::get<0>(pp).prop_part(); + DCOUT(fmt::format(" node_index = {}, prop name {}", item, prop_name)); + + variant.props()[prop_name] = std::get<1>(pp); + } + + VariantSetSpec &vs = parentPrimSpec->variantSets()[variantSetName]; + + if (vs.name.empty()) { + vs.name = variantSetName; + } + vs.variantSet[variantName] = variant; + + } + + if (_variantPrimChildren.count(current)) { + + // - currentPrim <- current + // - variant Prim children + + if (!primspec) { + PUSH_ERROR_AND_RETURN("Internal error: must be Prim."); + } + + DCOUT(fmt::format("{} has variant PrimSpec ", primspec->name())); + + + for (const auto &item : _variantPrimChildren.at(current)) { + + if (!_variantPrimSpecs.count(item)) { + PUSH_ERROR_AND_RETURN("Internal error: variant Prim children not found."); + } + + const PrimSpec &vp = _variantPrimSpecs.at(item); + + DCOUT(fmt::format(" variantPrim name {}", vp.name())); + + // element_name must be variant: "{variant=value}" + if (!is_variantElementName(vp.name())) { + PUSH_ERROR_AND_RETURN("Corrupted Crate. Variant Prim has invalid element_name."); + } + + std::array toks; + if (!tokenize_variantElement(vp.name(), &toks)) { + PUSH_ERROR_AND_RETURN("Invalid variant element_name."); + } + + std::string variantSetName = toks[0]; + std::string variantName = toks[1]; + + VariantSetSpec &vs = primspec->variantSets()[variantSetName]; + + if (vs.name.empty()) { + vs.name = variantSetName; + } + vs.variantSet[variantName].metas() = vp.metas(); + DCOUT("# of primChildren = " << vp.children().size()); + vs.variantSet[variantName].children() = std::move(vp.children()); + + } + } + + if (parent == 0) { // root prim + if (primspec) { + layer->primspecs()[primspec.value().name()] = std::move(primspec.value()); + } + } else { + if (_variantPrimSpecs.count(parent)) { + // Add to variantPrim + DCOUT("parent is variantPrim: " << parent); + if (!primspec) { + // FIXME: Validate current should be Prim. + PUSH_WARN("parent is variantPrim, but current is not Prim."); + } else { + DCOUT("Adding prim to child..."); + PrimSpec &vps = _variantPrimSpecs.at(parent); + vps.children().emplace_back(std::move(primspec.value())); + } + } else if (primspec && parentPrimSpec) { + // Add to parent prim. + parentPrimSpec->children().emplace_back(std::move(primspec.value())); + } + } + + return true; +} + +bool USDCReader::Impl::ToLayer(Layer *layer) { + + if (!layer) { + PUSH_ERROR_AND_RETURN("`layer` argument is nullptr."); + } + + // format test + DCOUT(fmt::format("# of Paths = {}", crate_reader->NumPaths())); + + if (crate_reader->NumNodes() == 0) { + PUSH_WARN("Empty scene."); + return true; + } + + // TODO: Directly access data in crate_reader. + _nodes = crate_reader->GetNodes(); + _specs = crate_reader->GetSpecs(); + _fields = crate_reader->GetFields(); + _fieldset_indices = crate_reader->GetFieldsetIndices(); + _paths = crate_reader->GetPaths(); + _elemPaths = crate_reader->GetElemPaths(); + _live_fieldsets = crate_reader->GetLiveFieldSets(); + + PathIndexToSpecIndexMap + path_index_to_spec_index_map; // path_index -> spec_index + + { + for (size_t i = 0; i < _specs.size(); i++) { + if (_specs[i].path_index.value == ~0u) { + continue; + } + + // path_index should be unique. + if (path_index_to_spec_index_map.count(_specs[i].path_index.value) != 0) { + PUSH_ERROR_AND_RETURN("Multiple PathIndex found in Crate data."); + } + + DCOUT(fmt::format("path index[{}] -> spec index [{}]", + _specs[i].path_index.value, uint32_t(i))); + path_index_to_spec_index_map[_specs[i].path_index.value] = uint32_t(i); + } + } + + layer->primspecs().clear(); + + int root_node_id = 0; + bool ret = ReconstructPrimSpecRecursively(/* no further root for root_node */ -1, + root_node_id, /* root Prim */ nullptr, + /* level */ 0, + path_index_to_spec_index_map, layer); + + if (!ret) { + PUSH_ERROR_AND_RETURN("Failed to reconstruct Layer(PrimSpec hierarchy)"); + } + + //stage->compute_absolute_prim_path_and_assign_prim_id(); + + return true; +} + +bool USDCReader::Impl::ReadUSDC() { + if (crate_reader) { + delete crate_reader; + } + + // TODO: Setup CrateReaderConfig. + crate::CrateReaderConfig config; + + // Transfer settings + config.numThreads = _config.numThreads; + + size_t sz_mb = _config.kMaxAllowedMemoryInMB; + if (sizeof(size_t) == 4) { + // 32bit + // cap to 2GB + sz_mb = (std::min)(size_t(1024 * 2), sz_mb); + + config.maxMemoryBudget = sz_mb * 1024 * 1024; + } else { + config.maxMemoryBudget = _config.kMaxAllowedMemoryInMB * 1024ull * 1024ull; + } + + crate_reader = new crate::CrateReader(_sr, config); + + _warn.clear(); + _err.clear(); + + if (!crate_reader->ReadBootStrap()) { + _warn = crate_reader->GetWarning(); + _err = crate_reader->GetError(); + return false; + } + + if (!crate_reader->ReadTOC()) { + _warn = crate_reader->GetWarning(); + _err = crate_reader->GetError(); + return false; + } + + // Read known sections + + if (!crate_reader->ReadTokens()) { + _warn = crate_reader->GetWarning(); + _err = crate_reader->GetError(); + return false; + } + + if (!crate_reader->ReadStrings()) { + _warn = crate_reader->GetWarning(); + _err = crate_reader->GetError(); + return false; + } + + if (!crate_reader->ReadFields()) { + _warn = crate_reader->GetWarning(); + _err = crate_reader->GetError(); + return false; + } + + if (!crate_reader->ReadFieldSets()) { + _warn = crate_reader->GetWarning(); + _err = crate_reader->GetError(); + return false; + } + + if (!crate_reader->ReadPaths()) { + _warn = crate_reader->GetWarning(); + _err = crate_reader->GetError(); + return false; + } + + if (!crate_reader->ReadSpecs()) { + _warn = crate_reader->GetWarning(); + _err = crate_reader->GetError(); + return false; + } + + // TODO(syoyo): Read unknown sections + + /// + /// Reconstruct C++ representation of USD scene graph. + /// + DCOUT("BuildLiveFieldSets"); + if (!crate_reader->BuildLiveFieldSets()) { + _warn = crate_reader->GetWarning(); + _err = crate_reader->GetError(); + + return false; + } + + _warn += crate_reader->GetWarning(); + _err += crate_reader->GetError(); + + DCOUT("Read Crate."); + + return true; +} + +// +// -- Interface -- +// +USDCReader::USDCReader(StreamReader *sr, const USDCReaderConfig &config) { + impl_ = new USDCReader::Impl(sr, config); +} + +USDCReader::~USDCReader() { + delete impl_; + impl_ = nullptr; +} + +void USDCReader::set_reader_config(const USDCReaderConfig &config) { + impl_->set_reader_config(config); +} + +const USDCReaderConfig USDCReader::get_reader_config() const { + return impl_->get_reader_config(); +} + +bool USDCReader::ReconstructStage(Stage *stage) { + DCOUT("Reconstruct Stage."); + return impl_->ReconstructStage(stage); +} + +bool USDCReader::get_as_layer(Layer *layer) { + return impl_->ToLayer(layer); +} + +std::string USDCReader::GetError() { return impl_->GetError(); } + +std::string USDCReader::GetWarning() { return impl_->GetWarning(); } + +bool USDCReader::ReadUSDC() { return impl_->ReadUSDC(); } + +} // namespace usdc +} // namespace tinyusdz + +#else // TINYUSDZ_DISABLE_MODULE_USDC_READER + +namespace tinyusdz { +namespace usdc { + +// +// -- Interface -- +// +USDCReader::USDCReader(StreamReader *sr, USDCReaderConfig &config) { + (void)sr; + (void)config; +} + +USDCReader::~USDCReader() {} + +void USDCReader::set_reader_config(const USDCReaderConfig &config) { + (void)config; +} + +const USDCReaderConfig USDCReader::get_reader_config() const { + return USDCReaderConfig(); +} + +bool USDCReader::ReconstructStage(Stage *stage) { + (void)scene; + DCOUT("Reconstruct Stage."); + return false; +} + +bool USDCReader::get_as_layer(Layer *layer) { + (void)layer; + return false; +} + +std::string USDCReader::GetError() { + return "USDC reader feature is disabled in this build.\n"; +} + +std::string USDCReader::GetWarning() { return ""; } + +} // namespace usdc +} // namespace tinyusdz + +#endif // TINYUSDZ_DISABLE_MODULE_USDC_READER diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usdc-reader.hh b/contrib/tinyusdz/tinyusdz_repo/src/usdc-reader.hh new file mode 100644 index 000000000..89d6a8c70 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usdc-reader.hh @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2020-2022 Syoyo Fujita. +// Copyright 2023-Present Light Transport Entertainment Inc. +// +#pragma once + +#include "stream-reader.hh" +#include "tinyusdz.hh" + +namespace tinyusdz { +namespace usdc { + +/// +/// USDC(Crate) reader +/// + +struct USDCReaderConfig { + int32_t numThreads = -1; // -1 = use system's # of threads + uint32_t kMaxPrimNestLevel = 256; + uint32_t kMaxFieldValuePairs = 4096; + uint32_t kMaxTokenLength = 4096; // Max length of `token` + uint32_t kMaxStringLength = 1024*1024*64; // Max length of `string` data + uint32_t kMaxElementSize = 512; // Max allowed value for `elementSize` + size_t kMaxAllowedMemoryInMB = 1024*16; //Max allowed memory usage in [mb] + + bool allow_unknown_prims = true; + bool allow_unknown_apiSchemas = true; + + bool strict_allowedToken_check = false; +}; + +class USDCReader { + public: + USDCReader(StreamReader *sr, + const USDCReaderConfig &config = USDCReaderConfig()); + ~USDCReader(); + + void set_reader_config(const USDCReaderConfig &config); + const USDCReaderConfig get_reader_config() const; + + bool ReadUSDC(); + + bool ReconstructStage(Stage *stage); + + // For composition. + bool get_as_layer(Layer *layer); + + // Approximated memory usage in [mb] + size_t GetMemoryUsage() const; + + std::string GetError(); + std::string GetWarning(); + + private: + class Impl; + Impl *impl_{}; +}; + +} // namespace usdc +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usdc-writer.cc b/contrib/tinyusdz/tinyusdz_repo/src/usdc-writer.cc new file mode 100644 index 000000000..69ba5126a --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usdc-writer.cc @@ -0,0 +1,597 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. + +#include "usdc-writer.hh" + +#if !defined(TINYUSDZ_DISABLE_MODULE_USDC_WRITER) + +#if defined(_MSC_VER) || defined(__MINGW32__) +#if defined(__clang__) +// No need to define NOMINMAX for llvm-mingw +#else +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include // include API for expanding a file path + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#endif + + +#include +#include +#include + +#include "crate-format.hh" +#include "io-util.hh" +#include "lz4-compression.hh" +#include "token-type.hh" + +#include "common-macros.inc" + +namespace tinyusdz { +namespace usdc { + +namespace { + +constexpr size_t kSectionNameMaxLength = 15; + +#ifdef _WIN32 +std::wstring UTF8ToWchar(const std::string &str) { + int wstr_size = + MultiByteToWideChar(CP_UTF8, 0, str.data(), int(str.size()), nullptr, 0); + std::wstring wstr(size_t(wstr_size), 0); + MultiByteToWideChar(CP_UTF8, 0, str.data(), int(str.size()), &wstr[0], + int(wstr.size())); + return wstr; +} + +#if 0 +std::string WcharToUTF8(const std::wstring &wstr) { + int str_size = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), int(wstr.size()), + nullptr, 0, nullptr, nullptr); + std::string str(size_t(str_size), 0); + WideCharToMultiByte(CP_UTF8, 0, wstr.data(), int(wstr.size()), &str[0], + int(str.size()), nullptr, nullptr); + return str; +} +#endif +#endif + +struct Section { + Section() { memset(this, 0, sizeof(*this)); } + Section(char const *name, int64_t start, int64_t size); + char name[kSectionNameMaxLength + 1]; + int64_t start, size; // byte offset to section info and its data size +}; + +// +// TOC = list of sections. +// +struct TableOfContents { + // Section const *GetSection(SectionName) const; + // int64_t GetMinimumSectionStart() const; + std::vector
sections; +}; + +//struct Field { +// // FIXME(syoyo): Do we need 4 bytes padding as done in pxrUSD? +// // uint32_t padding_; +// +// crate::TokenIndex token_index; +// crate::ValueRep value_rep; +//}; + +#if 0 +// For unordered_map + +// https://stackoverflow.com/questions/8513911/how-to-create-a-good-hash-combine-with-64-bit-output-inspired-by-boosthash-co +// From CityHash code. +template +inline void hash_combine(std::size_t &seed, const T &v) { +#ifdef __wasi__ // 32bit platform + // Use boost version. + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +#else + std::hash hasher; + const uint64_t kMul = 0x9ddfea08eb382d69ULL; + std::size_t a = (hasher(v) ^ seed) * kMul; + a ^= (a >> 47); + std::size_t b = (seed ^ a) * kMul; + b ^= (b >> 47); + seed = b * kMul; +#endif +} + +struct PathHasher { + size_t operator()(const Path &path) const { + size_t seed = std::hash()(path.GetPrimPart()); + hash_combine(seed, std::hash()(path.GetPropPart())); + hash_combine(seed, std::hash()(path.GetLocalPart())); + hash_combine(seed, std::hash()(path.IsValid())); + + return seed; + } +}; + +struct PathKeyEqual { + bool operator()(const Path &lhs, const Path &rhs) const { + bool ret = lhs.GetPrimPart() == rhs.GetPrimPart(); + ret &= lhs.GetPropPart() == rhs.GetPropPart(); + ret &= lhs.GetLocalPart() == rhs.GetLocalPart(); + ret &= lhs.IsValid() == rhs.IsValid(); + + return ret; + } +}; + +struct FieldHasher { + size_t operator()(const Field &field) const { + size_t seed = std::hash()(field.token_index.value); + hash_combine(seed, std::hash()(field.value_rep.GetData())); + + return seed; + } +}; + +struct FieldKeyEqual { + bool operator()(const Field &lhs, const Field &rhs) const { + bool ret = lhs.token_index == rhs.token_index; + ret &= lhs.value_rep == rhs.value_rep; + + return ret; + } +}; + +struct FieldSetHasher { + size_t operator()(const std::vector &fieldset) const { + if (fieldset.empty()) { + return 0; + } + + size_t seed = std::hash()(fieldset[0].value); + for (size_t i = 1; i < fieldset.size(); i++) { + hash_combine(seed, std::hash()(fieldset[i].value)); + } + + return seed; + } +}; +#endif + +class Packer { + public: + crate::TokenIndex AddToken(const Token &token); + crate::StringIndex AddString(const std::string &str); + crate::PathIndex AddPath(const Path &path); + crate::FieldIndex AddField(const crate::Field &field); + crate::FieldSetIndex AddFieldSet( + const std::vector &field_indices); + + const std::vector &GetTokens() const { return tokens_; } + + private: + std::unordered_map + token_to_index_map; + std::unordered_map string_to_index_map; + std::unordered_map + path_to_index_map; + std::unordered_map + field_to_index_map; + std::unordered_map, crate::FieldSetIndex, + crate::FieldSetHasher> + fieldset_to_index_map; + + std::vector tokens_; + std::vector strings_; + std::vector paths_; + std::vector fields_; + std::vector + fieldsets_; // flattened 1D array of FieldSets. Each span is terminated + // by Index()(= ~0) +}; + +crate::TokenIndex Packer::AddToken(const Token &token) { + if (token_to_index_map.count(token)) { + return token_to_index_map[token]; + } + + // index = size of umap + token_to_index_map[token] = crate::TokenIndex(uint32_t(tokens_.size())); + tokens_.emplace_back(token); + + return token_to_index_map[token]; +} + +crate::StringIndex Packer::AddString(const std::string &str) { + if (string_to_index_map.count(str)) { + return string_to_index_map[str]; + } + + // index = size of umap + string_to_index_map[str] = crate::StringIndex(uint32_t(strings_.size())); + strings_.emplace_back(str); + + return string_to_index_map[str]; +} + +crate::PathIndex Packer::AddPath(const Path &path) { + if (path_to_index_map.count(path)) { + return path_to_index_map[path]; + } + + // index = size of umap + path_to_index_map[path] = crate::PathIndex(uint32_t(paths_.size())); + paths_.emplace_back(path); + + return path_to_index_map[path]; +} + +crate::FieldIndex Packer::AddField(const crate::Field &field) { + if (field_to_index_map.count(field)) { + return field_to_index_map[field]; + } + + // index = size of umap + field_to_index_map[field] = crate::FieldIndex(uint32_t(fields_.size())); + fields_.emplace_back(field); + + return field_to_index_map[field]; +} + +crate::FieldSetIndex Packer::AddFieldSet( + const std::vector &fieldset) { + if (fieldset_to_index_map.count(fieldset)) { + return fieldset_to_index_map[fieldset]; + } + + // index = size of umap = star index of FieldSet span. + fieldset_to_index_map[fieldset] = + crate::FieldSetIndex(uint32_t(fieldsets_.size())); + + fieldsets_.insert(fieldsets_.end(), fieldset.begin(), fieldset.end()); + fieldsets_.push_back(crate::FieldIndex()); // terminator(~0) + + return fieldset_to_index_map[fieldset]; +} + +class Writer { + public: + Writer(const Stage &stage) : stage_(stage) {} + + const Stage &stage_; + + const std::string &GetError() const { return err_; } + const std::string &GetWarning() const { return warn_; } + + void PushError(const std::string &s) { + err_ += s; + } + + void PushWarn(const std::string &s) { + warn_ += s; + } + + bool WriteHeader(uint64_t toc_offset) { + char magic[8]; + magic[0] = 'P'; + magic[1] = 'X'; + magic[2] = 'R'; + magic[3] = '-'; + magic[4] = 'U'; + magic[5] = 'S'; + magic[6] = 'D'; + magic[7] = 'C'; + + uint8_t version[8]; // Only first 3 bytes are used. + version[0] = 0; + version[1] = 8; + version[2] = 0; + + std::array header; + memset(&header, 0, 88); + + memcpy(&header[0], magic, 8); + memcpy(&header[8], version, 8); + memcpy(&header[16], &toc_offset, 8); + + oss_.write(reinterpret_cast(&header[0]), 88); + + return true; + } + + bool WriteTokens() { + // Build single string separated by '\0', then compress it with lz4 + std::ostringstream oss; + + auto tokens = packer_.GetTokens(); + + for (size_t i = 0; i < tokens.size(); i++) { + oss << tokens[i].str(); + + if (i != (tokens.size() - 1)) { + oss.put('\0'); // separator + } + } + // Last string does not terminated with `\0' + + // compress + size_t input_bytes = oss.str().size(); + if (input_bytes == 0) { + PUSH_ERROR("Invalid data size."); + return false; + } + + std::vector buf; + buf.resize(LZ4Compression::GetCompressedBufferSize(input_bytes)); + + std::string err; + size_t n = LZ4Compression::CompressToBuffer(oss.str().data(), buf.data(), + input_bytes, &err); + + (void)n; + + if (!err.empty()) { + PUSH_ERROR(err); + return false; + } + + return true; + } + + bool WriteStrings() { return false; } + + bool WriteFields() { return false; } + + bool WriteFieldSets() { return false; } + + bool WritePaths() { return false; } + + bool WriteSpecs() { return false; } + + bool WriteTOC() { + uint64_t num_sections = toc_.sections.size(); + + DCOUT("# of sections = " << std::to_string(num_sections)); + + if (num_sections == 0) { + err_ += "Zero sections in TOC.\n"; + return false; + } + + // # of sections + oss_.write(reinterpret_cast(&num_sections), 8); + + return true; + } + + bool Write() { + // + // - TOC + // - Tokens + // - Strings + // - Fields + // - FieldSets + // - Paths + // - Specs + // + + if (!WriteTokens()) { + PUSH_ERROR("Failed to write Tokens."); + return false; + } + + if (!WriteStrings()) { + PUSH_ERROR("Failed to write Strings."); + return false; + } + + if (!WriteFields()) { + PUSH_ERROR("Failed to write Fields."); + return false; + } + + if (!WriteFieldSets()) { + PUSH_ERROR("Failed to write FieldSets."); + return false; + } + + if (!WritePaths()) { + PUSH_ERROR("Failed to write Paths."); + return false; + } + + if (!WriteSpecs()) { + PUSH_ERROR("Failed to write Specs."); + return false; + } + + // TODO(syoyo): Add feature to support writing unknown section(custom user + // data) + // if (!WriteUnknownSections()) { + // PUSH_ERROR("Failed to write custom sections."); + // return false; + //} + + const uint64_t toc_offset = static_cast(oss_.tellp()); + if (!WriteTOC()) { + PUSH_ERROR("Failed to write TOC."); + return false; + } + + // write header + oss_.seekp(0, std::ios::beg); + if (!WriteHeader(toc_offset)) { + PUSH_ERROR("Failed to write Header."); + return false; + } + + return true; + } + + // Get serialized USDC binary data + bool GetOutput(std::vector *output) { + if (!err_.empty()) { + return false; + } + + (void)output; + + // TODO + return false; + } + + private: + Writer() = delete; + Writer(const Writer &) = delete; + + TableOfContents toc_; + + Packer packer_; + + // + // Serialized data + // + std::ostringstream oss_; + + std::string err_; + std::string warn_; +}; + +} // namespace + +bool SaveAsUSDCToFile(const std::string &filename, const Stage &stage, + std::string *warn, std::string *err) { +#ifdef __ANDROID__ + (void)filename; + (void)stage; + (void)warn; + + if (err) { + (*err) += "Saving USDC to a file is not supported for Android platform(at the moment).\n"; + } + return false; +#else + + std::vector output; + + if (!SaveAsUSDCToMemory(stage, &output, warn, err)) { + return false; + } + +#ifdef _WIN32 +#if defined(_MSC_VER) || defined(__GLIBCXX__) || defined(__clang__) + FILE *fp = nullptr; + errno_t fperr = _wfopen_s(&fp, UTF8ToWchar(filename).c_str(), L"wb"); + if (fperr != 0) { + if (err) { + // TODO: WChar + (*err) += "Failed to open file to write.\n"; + } + return false; + } +#else + FILE *fp = nullptr; + errno_t fperr = fopen_s(&fp, filename.c_str(), "wb"); + if (fperr != 0) { + if (err) { + (*err) += "Failed to open file `" + filename + "` to write.\n"; + } + return false; + } +#endif + +#else + FILE *fp = fopen(filename.c_str(), "wb"); + if (fp == nullptr) { + if (err) { + (*err) += "Failed to open file `" + filename + "` to write.\n"; + } + return false; + } +#endif + + size_t n = fwrite(output.data(), /* size */ 1, /* count */ output.size(), fp); + if (n < output.size()) { + // TODO: Retry writing data when n < output.size() + + if (err) { + (*err) += "Failed to write data to a file.\n"; + } + return false; + } + + return true; +#endif +} + +bool SaveAsUSDCToMemory(const Stage &stage, std::vector *output, + std::string *warn, std::string *err) { + (void)warn; + (void)output; + + // TODO + Writer writer(stage); + + if (err) { + (*err) += "USDC writer is not yet implemented.\n"; + } + + return false; +} + +} // namespace usdc +} // namespace tinyusdz + +#else + +namespace tinyusdz { +namespace usdc { + +bool SaveAsUSDCToFile(const std::string &filename, const Scene &scene, + std::string *warn, std::string *err) { + (void)filename; + (void)scene; + (void)warn; + + if (err) { + (*err) = "USDC writer feature is disabled in this build.\n"; + } + + return false; +} + +bool SaveAsUSDCToMemory(const Scene &scene, std::vector *output, + std::string *warn, std::string *err) { + (void)scene; + (void)output; + (void)warn; + + if (err) { + (*err) = "USDC writer feature is disabled in this build.\n"; + } + + return false; +} + +} // namespace usdc +} // namespace tinyusdz + +#endif diff --git a/contrib/tinyusdz/tinyusdz_repo/src/usdc-writer.hh b/contrib/tinyusdz/tinyusdz_repo/src/usdc-writer.hh new file mode 100644 index 000000000..6e5b605d1 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/usdc-writer.hh @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +#pragma once + +#include "tinyusdz.hh" + +namespace tinyusdz { +namespace usdc { + +/// +/// Save scene as USDC(binary) to a file +/// +/// @param[in] filename USDC filename +/// @param[in] stage Stage +/// @param[out] warn Warning message +/// @param[out] err Error message +/// +/// @return true upon success. +/// +bool SaveAsUSDCToFile(const std::string &filename, const Stage &stage, + std::string *warn, std::string *err); + +/// +/// Save scene as USDC(binary) to a memory +/// +/// @param[in] stage Stage +/// @param[out] output Binary data +/// @param[out] warn Warning message +/// @param[out] err Error message +/// +/// @return true upon success. +/// +bool SaveAsUSDCToMemory(const Stage &stage, std::vector *output, + std::string *warn, std::string *err); + +} // namespace usdc +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/value-eval-util.hh b/contrib/tinyusdz/tinyusdz_repo/src/value-eval-util.hh new file mode 100644 index 000000000..58bd7ffa2 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/value-eval-util.hh @@ -0,0 +1,1948 @@ +#pragma once + +#include "value-types.hh" +#include "linear-algebra.hh" + +namespace tinyusdz { + +#define FOUR_ARITH_OP_2(__ty, __basety) \ +inline __ty operator+(const __ty &a, const __ty &b) { \ + return {a[0] + b[0], a[1] + b[1]}; \ +} \ +\ +inline __ty operator+(const __basety a, const __ty &b) { \ + return {a + b[0], a + b[1]}; \ +}\ +inline __ty operator+(const __ty &a, const __basety b) { \ + return {a[0] + b, a[1] + b}; \ +} \ +\ +inline __ty operator-(const __ty &a, const __ty &b) { \ + return {a[0] - b[0], a[1] - b[1]}; \ +} \ +\ +inline __ty operator-(const __basety a, const __ty &b) { \ + return {a - b[0], a - b[1]}; \ +}\ +inline __ty operator-(const __ty &a, const __basety b) { \ + return {a[0] - b, a[1] - b}; \ +}\ +\ +inline __ty operator*(const __ty &a, const __ty &b) { \ + return {a[0] * b[0], a[1] * b[1]}; \ +} \ +\ +inline __ty operator*(const __basety a, const __ty &b) { \ + return {a * b[0], a * b[1]}; \ +}\ +inline __ty operator*(const __ty &a, const __basety b) { \ + return {a[0] * b, a[1] * b}; \ +} \ +\ +inline __ty operator/(const __ty &a, const __ty &b) { \ + return {a[0] / b[0], a[1] / b[1]}; \ +} \ +\ +inline __ty operator/(const __basety a, const __ty &b) { \ + return {a / b[0], a / b[1]}; \ +}\ +inline __ty operator/(const __ty &a, const __basety b) { \ + return {a[0] / b, a[1] / b}; \ +} + +#define FOUR_ARITH_OP_3(__ty, __basety) \ +inline __ty operator+(const __ty &a, const __ty &b) { \ + return {a[0] + b[0], a[1] + b[1], a[2] + b[2]}; \ +} \ +\ +inline __ty operator+(const __basety a, const __ty &b) { \ + return {a + b[0], a + b[1], a + b[2]}; \ +}\ +inline __ty operator+(const __ty &a, const __basety b) { \ + return {a[0] + b, a[1] + b, a[2] + b}; \ +} \ +\ +inline __ty operator-(const __ty &a, const __ty &b) { \ + return {a[0] - b[0], a[1] - b[1], a[2] - b[2]}; \ +} \ +\ +inline __ty operator-(const __basety a, const __ty &b) { \ + return {a - b[0], a - b[1], a - b[2]}; \ +}\ +inline __ty operator-(const __ty &a, const __basety b) { \ + return {a[0] - b, a[1] - b, a[2] - b}; \ +}\ +\ +inline __ty operator*(const __ty &a, const __ty &b) { \ + return {a[0] * b[0], a[1] * b[1], a[2] * b[2]}; \ +} \ +\ +inline __ty operator*(const __basety a, const __ty &b) { \ + return {a * b[0], a * b[1], a * b[2]}; \ +}\ +inline __ty operator*(const __ty &a, const __basety b) { \ + return {a[0] * b, a[1] * b, a[2] * b}; \ +} \ +\ +inline __ty operator/(const __ty &a, const __ty &b) { \ + return {a[0] / b[0], a[1] / b[1], a[2] / b[2]}; \ +} \ +\ +inline __ty operator/(const __basety a, const __ty &b) { \ + return {a / b[0], a / b[1], a / b[2]}; \ +}\ +inline __ty operator/(const __ty &a, const __basety b) { \ + return {a[0] / b, a[1] / b, a[2] / b}; \ +} + +#define FOUR_ARITH_OP_4(__ty, __basety) \ +inline __ty operator+(const __ty &a, const __ty &b) { \ + return {a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]}; \ +} \ +\ +inline __ty operator+(const __basety a, const __ty &b) { \ + return {a + b[0], a + b[1], a + b[2], a + b[3]}; \ +}\ +inline __ty operator+(const __ty &a, const __basety b) { \ + return {a[0] + b, a[1] + b, a[2] + b, a[3] + b}; \ +} \ +\ +inline __ty operator-(const __ty &a, const __ty &b) { \ + return {a[0] - b[0], a[1] - b[1], a[2] - b[2], a[3] - b[3]}; \ +} \ +\ +inline __ty operator-(const __basety a, const __ty &b) { \ + return {a - b[0], a - b[1], a - b[2], a - b[3]}; \ +}\ +inline __ty operator-(const __ty &a, const __basety b) { \ + return {a[0] - b, a[1] - b, a[2] - b, a[3] - b}; \ +}\ +\ +inline __ty operator*(const __ty &a, const __ty &b) { \ + return {a[0] * b[0], a[1] * b[1], a[2] * b[2], a[3] * b[3]}; \ +} \ +\ +inline __ty operator*(const __basety a, const __ty &b) { \ + return {a * b[0], a * b[1], a * b[2], a * b[3]}; \ +}\ +inline __ty operator*(const __ty &a, const __basety b) { \ + return {a[0] * b, a[1] * b, a[2] * b, a[3] * b}; \ +} \ +\ +inline __ty operator/(const __ty &a, const __ty &b) { \ + return {a[0] / b[0], a[1] / b[1], a[2] / b[2], a[3] / b[3]}; \ +} \ +\ +inline __ty operator/(const __basety a, const __ty &b) { \ + return {a / b[0], a / b[1], a / b[2], a / b[3]}; \ +}\ +inline __ty operator/(const __ty &a, const __basety b) { \ + return {a[0] / b, a[1] / b, a[2] / b, a[3] / b}; \ +} + +#define ARITH_ASSIGN_OP_2(__ty, __basety) \ +inline __ty &operator+=(__ty &a, const __ty &b) { \ + a[0] += b[0]; a[1] += b[1]; \ + return a; \ +} \ +inline __ty &operator-=(__ty &a, const __ty &b) { \ + a[0] -= b[0]; a[1] -= b[1]; \ + return a; \ +} \ +inline __ty &operator*=(__ty &a, const __ty &b) { \ + a[0] *= b[0]; a[1] *= b[1]; \ + return a; \ +} \ +inline __ty &operator/=(__ty &a, const __ty &b) { \ + a[0] /= b[0]; a[1] /= b[1]; \ + return a; \ +} \ + +#define ARITH_ASSIGN_OP_3(__ty, __basety) \ +inline __ty &operator+=(__ty &a, const __ty &b) { \ + a[0] += b[0]; a[1] += b[1]; a[2] += b[2]; \ + return a; \ +} \ +inline __ty &operator-=(__ty &a, const __ty &b) { \ + a[0] -= b[0]; a[1] -= b[1]; a[2] -= b[2]; \ + return a; \ +} \ +inline __ty &operator*=(__ty &a, const __ty &b) { \ + a[0] *= b[0]; a[1] *= b[1]; a[2] *= b[2]; \ + return a; \ +} \ +inline __ty &operator/=(__ty &a, const __ty &b) { \ + a[0] /= b[0]; a[1] /= b[1]; a[2] /= b[2]; \ + return a; \ +} \ + +#define ARITH_ASSIGN_OP_4(__ty, __basety) \ +inline __ty &operator+=(__ty &a, const __ty &b) { \ + a[0] += b[0]; a[1] += b[1]; a[2] += b[2]; a[3] += b[3]; \ + return a; \ +} \ +inline __ty &operator-=(__ty &a, const __ty &b) { \ + a[0] -= b[0]; a[1] -= b[1]; a[2] -= b[2]; a[3] -= b[3]; \ + return a; \ +} \ +inline __ty &operator*=(__ty &a, const __ty &b) { \ + a[0] *= b[0]; a[1] *= b[1]; a[2] *= b[2]; a[3] *= b[3]; \ + return a; \ +} \ +inline __ty &operator/=(__ty &a, const __ty &b) { \ + a[0] /= b[0]; a[1] /= b[1]; a[2] /= b[2]; a[3] /= b[3]; \ + return a; \ +} \ + +// TODO: half op scalar_half +FOUR_ARITH_OP_2(value::half2, float) +FOUR_ARITH_OP_3(value::half3, float) +FOUR_ARITH_OP_4(value::half4, float) +ARITH_ASSIGN_OP_2(value::half2, float) +ARITH_ASSIGN_OP_3(value::half3, float) +ARITH_ASSIGN_OP_4(value::half4, float) + +FOUR_ARITH_OP_2(value::int2, int) +FOUR_ARITH_OP_3(value::int3, int) +FOUR_ARITH_OP_4(value::int4, int) +ARITH_ASSIGN_OP_2(value::int2, int) +ARITH_ASSIGN_OP_3(value::int3, int) +ARITH_ASSIGN_OP_4(value::int4, int) + +FOUR_ARITH_OP_2(value::uint2, uint32_t) +FOUR_ARITH_OP_3(value::uint3, uint32_t) +FOUR_ARITH_OP_4(value::uint4, uint32_t) +ARITH_ASSIGN_OP_2(value::uint2, uint32_t) +ARITH_ASSIGN_OP_3(value::uint3, uint32_t) +ARITH_ASSIGN_OP_4(value::uint4, uint32_t) + +FOUR_ARITH_OP_2(value::float2, float) +FOUR_ARITH_OP_3(value::float3, float) +FOUR_ARITH_OP_4(value::float4, float) +ARITH_ASSIGN_OP_2(value::float2, float) +ARITH_ASSIGN_OP_3(value::float3, float) +ARITH_ASSIGN_OP_4(value::float4, float) + +FOUR_ARITH_OP_2(value::double2, double) +FOUR_ARITH_OP_3(value::double3, double) +FOUR_ARITH_OP_4(value::double4, double) +ARITH_ASSIGN_OP_2(value::double2, double) +ARITH_ASSIGN_OP_3(value::double3, double) +ARITH_ASSIGN_OP_4(value::double4, double) + +FOUR_ARITH_OP_3(value::normal3h, float) +FOUR_ARITH_OP_3(value::normal3f, float) +FOUR_ARITH_OP_3(value::normal3d, double) +ARITH_ASSIGN_OP_3(value::normal3h, float) +ARITH_ASSIGN_OP_3(value::normal3f, float) +ARITH_ASSIGN_OP_3(value::normal3d, double) + +FOUR_ARITH_OP_3(value::vector3h, float) +FOUR_ARITH_OP_3(value::vector3f, float) +FOUR_ARITH_OP_3(value::vector3d, double) +ARITH_ASSIGN_OP_3(value::vector3h, float) +ARITH_ASSIGN_OP_3(value::vector3f, float) +ARITH_ASSIGN_OP_3(value::vector3d, double) + +FOUR_ARITH_OP_3(value::point3h, float) +FOUR_ARITH_OP_3(value::point3f, float) +FOUR_ARITH_OP_3(value::point3d, double) +ARITH_ASSIGN_OP_3(value::point3h, float) +ARITH_ASSIGN_OP_3(value::point3f, float) +ARITH_ASSIGN_OP_3(value::point3d, double) + +FOUR_ARITH_OP_3(value::color3h, float) +FOUR_ARITH_OP_3(value::color3f, float) +FOUR_ARITH_OP_3(value::color3d, double) +ARITH_ASSIGN_OP_3(value::color3h, float) +ARITH_ASSIGN_OP_3(value::color3f, float) +ARITH_ASSIGN_OP_3(value::color3d, double) + +FOUR_ARITH_OP_4(value::color4h, float) +FOUR_ARITH_OP_4(value::color4f, float) +FOUR_ARITH_OP_4(value::color4d, double) +ARITH_ASSIGN_OP_4(value::color4h, float) +ARITH_ASSIGN_OP_4(value::color4f, float) +ARITH_ASSIGN_OP_4(value::color4d, double) + +FOUR_ARITH_OP_2(value::texcoord2h, float) +FOUR_ARITH_OP_2(value::texcoord2f, float) +FOUR_ARITH_OP_2(value::texcoord2d, double) +ARITH_ASSIGN_OP_2(value::texcoord2h, float) +ARITH_ASSIGN_OP_2(value::texcoord2f, float) +ARITH_ASSIGN_OP_2(value::texcoord2d, double) + +FOUR_ARITH_OP_3(value::texcoord3h, float) +FOUR_ARITH_OP_3(value::texcoord3f, float) +FOUR_ARITH_OP_3(value::texcoord3d, double) +ARITH_ASSIGN_OP_3(value::texcoord3h, float) +ARITH_ASSIGN_OP_3(value::texcoord3f, float) +ARITH_ASSIGN_OP_3(value::texcoord3d, double) + +#undef FOUR_ARITH_OP_2 +#undef FOUR_ARITH_OP_3 +#undef FOUR_ARITH_OP_4 + +inline value::matrix2f operator+(const value::matrix2f &a, const double b) { + value::matrix2f dst; + dst.m[0][0] = float(double(a.m[0][0]) + b); + dst.m[0][1] = float(double(a.m[0][1]) + b); + dst.m[1][0] = float(double(a.m[1][0]) + b); + dst.m[1][1] = float(double(a.m[1][1]) + b); + + return dst; +} + +inline value::matrix2f operator+(const double a, const value::matrix2f &b) { + value::matrix2f dst; + dst.m[0][0] = float(a + double(b.m[0][0])); + dst.m[0][1] = float(a + double(b.m[0][1])); + dst.m[1][0] = float(a + double(b.m[1][0])); + dst.m[1][1] = float(a + double(b.m[1][1])); + + return dst; +} + + +inline value::matrix2f operator-(const value::matrix2f &a, const double b) { + value::matrix2f dst; + dst.m[0][0] = float(double(a.m[0][0]) - b); + dst.m[0][1] = float(double(a.m[0][1]) - b); + dst.m[1][0] = float(double(a.m[1][0]) - b); + dst.m[1][1] = float(double(a.m[1][1]) - b); + + return dst; +} + +inline value::matrix2f operator-(const double a, const value::matrix2f &b) { + value::matrix2f dst; + dst.m[0][0] = float(a - double(b.m[0][0])); + dst.m[0][1] = float(a - double(b.m[0][1])); + dst.m[1][0] = float(a - double(b.m[1][0])); + dst.m[1][1] = float(a - double(b.m[1][1])); + + return dst; +} + +inline value::matrix2f operator*(const value::matrix2f &a, const double b) { + value::matrix2f dst; + dst.m[0][0] = float(double(a.m[0][0]) * b); + dst.m[0][1] = float(double(a.m[0][1]) * b); + dst.m[1][0] = float(double(a.m[1][0]) * b); + dst.m[1][1] = float(double(a.m[1][1]) * b); + + return dst; +} + +inline value::matrix2f operator*(const double a, const value::matrix2f &b) { + value::matrix2f dst; + dst.m[0][0] = float(a * double(b.m[0][0])); + dst.m[0][1] = float(a * double(b.m[0][1])); + dst.m[1][0] = float(a * double(b.m[1][0])); + dst.m[1][1] = float(a * double(b.m[1][1])); + + return dst; +} + +inline value::matrix2f operator/(const value::matrix2f &a, const double b) { + value::matrix2f dst; + dst.m[0][0] = float(double(a.m[0][0]) / b); + dst.m[0][1] = float(double(a.m[0][1]) / b); + dst.m[1][0] = float(double(a.m[1][0]) / b); + dst.m[1][1] = float(double(a.m[1][1]) / b); + + return dst; +} + +inline value::matrix2f operator/(const double a, const value::matrix2f &b) { + value::matrix2f dst; + dst.m[0][0] = float(a / double(b.m[0][0])); + dst.m[0][1] = float(a / double(b.m[0][1])); + dst.m[1][0] = float(a / double(b.m[1][0])); + dst.m[1][1] = float(a / double(b.m[1][1])); + + return dst; +} + +inline value::matrix3f operator+(const value::matrix3f &a, const double b) { + value::matrix3f dst; + dst.m[0][0] = float(double(a.m[0][0]) + b); + dst.m[0][1] = float(double(a.m[0][1]) + b); + dst.m[0][2] = float(double(a.m[0][2]) + b); + dst.m[1][0] = float(double(a.m[1][0]) + b); + dst.m[1][1] = float(double(a.m[1][1]) + b); + dst.m[1][2] = float(double(a.m[1][2]) + b); + dst.m[2][0] = float(double(a.m[2][0]) + b); + dst.m[2][1] = float(double(a.m[2][1]) + b); + dst.m[2][2] = float(double(a.m[2][2]) + b); + + return dst; +} + +inline value::matrix3f operator+(const double a, const value::matrix3f &b) { + value::matrix3f dst; + dst.m[0][0] = float(a + double(b.m[0][0])); + dst.m[0][1] = float(a + double(b.m[0][1])); + dst.m[0][2] = float(a + double(b.m[0][2])); + dst.m[1][0] = float(a + double(b.m[1][0])); + dst.m[1][1] = float(a + double(b.m[1][1])); + dst.m[1][2] = float(a + double(b.m[1][2])); + dst.m[2][0] = float(a + double(b.m[2][0])); + dst.m[2][1] = float(a + double(b.m[2][1])); + dst.m[2][2] = float(a + double(b.m[2][2])); + + return dst; +} + + +inline value::matrix3f operator-(const value::matrix3f &a, const double b) { + value::matrix3f dst; + dst.m[0][0] = float(double(a.m[0][0] )- b); + dst.m[0][1] = float(double(a.m[0][1] )- b); + dst.m[0][2] = float(double(a.m[0][2] )- b); + dst.m[1][0] = float(double(a.m[1][0] )- b); + dst.m[1][1] = float(double(a.m[1][1] )- b); + dst.m[1][2] = float(double(a.m[1][2] )- b); + dst.m[2][0] = float(double(a.m[2][0] )- b); + dst.m[2][1] = float(double(a.m[2][1] )- b); + dst.m[2][2] = float(double(a.m[2][2] )- b); + + return dst; +} + +inline value::matrix3f operator-(const double a, const value::matrix3f &b) { + value::matrix3f dst; + dst.m[0][0] = float(a - double(b.m[0][0])); + dst.m[0][1] = float(a - double(b.m[0][1])); + dst.m[0][2] = float(a - double(b.m[0][2])); + dst.m[1][0] = float(a - double(b.m[1][0])); + dst.m[1][1] = float(a - double(b.m[1][1])); + dst.m[1][2] = float(a - double(b.m[1][2])); + dst.m[2][0] = float(a - double(b.m[2][0])); + dst.m[2][1] = float(a - double(b.m[2][1])); + dst.m[2][2] = float(a - double(b.m[2][2])); + + return dst; +} + +inline value::matrix3f operator*(const value::matrix3f &a, const double b) { + value::matrix3f dst; + dst.m[0][0] = float(double(a.m[0][0]) * b); + dst.m[0][1] = float(double(a.m[0][1]) * b); + dst.m[0][2] = float(double(a.m[0][2]) * b); + dst.m[1][0] = float(double(a.m[1][0]) * b); + dst.m[1][1] = float(double(a.m[1][1]) * b); + dst.m[1][2] = float(double(a.m[1][2]) * b); + dst.m[2][0] = float(double(a.m[2][0]) * b); + dst.m[2][1] = float(double(a.m[2][1]) * b); + dst.m[2][2] = float(double(a.m[2][2]) * b); + + return dst; +} + +inline value::matrix3f operator*(const double a, const value::matrix3f &b) { + value::matrix3f dst; + dst.m[0][0] = float(a * double(b.m[0][0])); + dst.m[0][1] = float(a * double(b.m[0][1])); + dst.m[0][2] = float(a * double(b.m[0][2])); + dst.m[1][0] = float(a * double(b.m[1][0])); + dst.m[1][1] = float(a * double(b.m[1][1])); + dst.m[1][2] = float(a * double(b.m[1][2])); + dst.m[2][0] = float(a * double(b.m[2][0])); + dst.m[2][1] = float(a * double(b.m[2][1])); + dst.m[2][2] = float(a * double(b.m[2][2])); + + return dst; +} + +inline value::matrix3f operator/(const value::matrix3f &a, const double b) { + value::matrix3f dst; + dst.m[0][0] = float(double(a.m[0][0]) / b); + dst.m[0][1] = float(double(a.m[0][1]) / b); + dst.m[0][2] = float(double(a.m[0][2]) / b); + dst.m[1][0] = float(double(a.m[1][0]) / b); + dst.m[1][1] = float(double(a.m[1][1]) / b); + dst.m[1][2] = float(double(a.m[1][2]) / b); + dst.m[2][0] = float(double(a.m[2][0]) / b); + dst.m[2][1] = float(double(a.m[2][1]) / b); + dst.m[2][2] = float(double(a.m[2][2]) / b); + + return dst; +} + +inline value::matrix3f operator/(const double a, const value::matrix3f &b) { + value::matrix3f dst; + dst.m[0][0] = float(a / double(b.m[0][0])); + dst.m[0][1] = float(a / double(b.m[0][1])); + dst.m[0][2] = float(a / double(b.m[0][2])); + dst.m[1][0] = float(a / double(b.m[1][0])); + dst.m[1][1] = float(a / double(b.m[1][1])); + dst.m[1][2] = float(a / double(b.m[1][2])); + dst.m[2][0] = float(a / double(b.m[2][0])); + dst.m[2][1] = float(a / double(b.m[2][1])); + dst.m[2][2] = float(a / double(b.m[2][2])); + + return dst; +} + +inline value::matrix4f operator+(const value::matrix4f &a, const double b) { + value::matrix4f dst; + dst.m[0][0] = float(double(a.m[0][0]) + b); + dst.m[0][1] = float(double(a.m[0][1]) + b); + dst.m[0][2] = float(double(a.m[0][2]) + b); + dst.m[0][3] = float(double(a.m[0][3]) + b); + dst.m[1][0] = float(double(a.m[1][0]) + b); + dst.m[1][1] = float(double(a.m[1][1]) + b); + dst.m[1][2] = float(double(a.m[1][2]) + b); + dst.m[1][3] = float(double(a.m[1][3]) + b); + dst.m[2][0] = float(double(a.m[2][0]) + b); + dst.m[2][1] = float(double(a.m[2][1]) + b); + dst.m[2][2] = float(double(a.m[2][2]) + b); + dst.m[2][3] = float(double(a.m[2][3]) + b); + dst.m[3][0] = float(double(a.m[3][0]) + b); + dst.m[3][1] = float(double(a.m[3][1]) + b); + dst.m[3][2] = float(double(a.m[3][2]) + b); + dst.m[3][3] = float(double(a.m[3][3]) + b); + + return dst; +} + +inline value::matrix4f operator+(const double a, const value::matrix4f &b) { + value::matrix4f dst; + dst.m[0][0] = float(a + double(b.m[0][0])); + dst.m[0][1] = float(a + double(b.m[0][1])); + dst.m[0][2] = float(a + double(b.m[0][2])); + dst.m[0][3] = float(a + double(b.m[0][3])); + dst.m[1][0] = float(a + double(b.m[1][0])); + dst.m[1][1] = float(a + double(b.m[1][1])); + dst.m[1][2] = float(a + double(b.m[1][2])); + dst.m[1][3] = float(a + double(b.m[1][3])); + dst.m[2][0] = float(a + double(b.m[2][0])); + dst.m[2][1] = float(a + double(b.m[2][1])); + dst.m[2][2] = float(a + double(b.m[2][2])); + dst.m[2][3] = float(a + double(b.m[2][3])); + dst.m[3][0] = float(a + double(b.m[3][0])); + dst.m[3][1] = float(a + double(b.m[3][1])); + dst.m[3][2] = float(a + double(b.m[3][2])); + dst.m[3][3] = float(a + double(b.m[3][3])); + + return dst; +} + +inline value::matrix4f operator-(const value::matrix4f &a, const double b) { + value::matrix4f dst; + dst.m[0][0] = float(double(a.m[0][0]) - b); + dst.m[0][1] = float(double(a.m[0][1]) - b); + dst.m[0][2] = float(double(a.m[0][2]) - b); + dst.m[0][3] = float(double(a.m[0][3]) - b); + dst.m[1][0] = float(double(a.m[1][0]) - b); + dst.m[1][1] = float(double(a.m[1][1]) - b); + dst.m[1][2] = float(double(a.m[1][2]) - b); + dst.m[1][3] = float(double(a.m[1][3]) - b); + dst.m[2][0] = float(double(a.m[2][0]) - b); + dst.m[2][1] = float(double(a.m[2][1]) - b); + dst.m[2][2] = float(double(a.m[2][2]) - b); + dst.m[2][3] = float(double(a.m[2][3]) - b); + dst.m[3][0] = float(double(a.m[3][0]) - b); + dst.m[3][1] = float(double(a.m[3][1]) - b); + dst.m[3][2] = float(double(a.m[3][2]) - b); + dst.m[3][3] = float(double(a.m[3][3]) - b); + + return dst; +} + +inline value::matrix4f operator-(const double a, const value::matrix4f &b) { + value::matrix4f dst; + dst.m[0][0] = float(a - double(b.m[0][0])); + dst.m[0][1] = float(a - double(b.m[0][1])); + dst.m[0][2] = float(a - double(b.m[0][2])); + dst.m[0][3] = float(a - double(b.m[0][3])); + dst.m[1][0] = float(a - double(b.m[1][0])); + dst.m[1][1] = float(a - double(b.m[1][1])); + dst.m[1][2] = float(a - double(b.m[1][2])); + dst.m[1][3] = float(a - double(b.m[1][3])); + dst.m[2][0] = float(a - double(b.m[2][0])); + dst.m[2][1] = float(a - double(b.m[2][1])); + dst.m[2][2] = float(a - double(b.m[2][2])); + dst.m[2][3] = float(a - double(b.m[2][3])); + dst.m[3][0] = float(a - double(b.m[3][0])); + dst.m[3][1] = float(a - double(b.m[3][1])); + dst.m[3][2] = float(a - double(b.m[3][2])); + dst.m[3][3] = float(a - double(b.m[3][3])); + + return dst; +} +inline value::matrix4f operator*(const value::matrix4f &a, const double b) { + value::matrix4f dst; + dst.m[0][0] = float(double(a.m[0][0]) * b); + dst.m[0][1] = float(double(a.m[0][1]) * b); + dst.m[0][2] = float(double(a.m[0][2]) * b); + dst.m[0][3] = float(double(a.m[0][3]) * b); + dst.m[1][0] = float(double(a.m[1][0]) * b); + dst.m[1][1] = float(double(a.m[1][1]) * b); + dst.m[1][2] = float(double(a.m[1][2]) * b); + dst.m[1][3] = float(double(a.m[1][3]) * b); + dst.m[2][0] = float(double(a.m[2][0]) * b); + dst.m[2][1] = float(double(a.m[2][1]) * b); + dst.m[2][2] = float(double(a.m[2][2]) * b); + dst.m[2][3] = float(double(a.m[2][3]) * b); + dst.m[3][0] = float(double(a.m[3][0]) * b); + dst.m[3][1] = float(double(a.m[3][1]) * b); + dst.m[3][2] = float(double(a.m[3][2]) * b); + dst.m[3][3] = float(double(a.m[3][3]) * b); + + return dst; +} + +inline value::matrix4f operator*(const double a, const value::matrix4f &b) { + value::matrix4f dst; + dst.m[0][0] = float(a * double(b.m[0][0])); + dst.m[0][1] = float(a * double(b.m[0][1])); + dst.m[0][2] = float(a * double(b.m[0][2])); + dst.m[0][3] = float(a * double(b.m[0][3])); + dst.m[1][0] = float(a * double(b.m[1][0])); + dst.m[1][1] = float(a * double(b.m[1][1])); + dst.m[1][2] = float(a * double(b.m[1][2])); + dst.m[1][3] = float(a * double(b.m[1][3])); + dst.m[2][0] = float(a * double(b.m[2][0])); + dst.m[2][1] = float(a * double(b.m[2][1])); + dst.m[2][2] = float(a * double(b.m[2][2])); + dst.m[2][3] = float(a * double(b.m[2][3])); + dst.m[3][0] = float(a * double(b.m[3][0])); + dst.m[3][1] = float(a * double(b.m[3][1])); + dst.m[3][2] = float(a * double(b.m[3][2])); + dst.m[3][3] = float(a * double(b.m[3][3])); + + return dst; +} + +inline value::matrix4f operator/(const value::matrix4f &a, const double b) { + value::matrix4f dst; + dst.m[0][0] = float(double(a.m[0][0]) / b); + dst.m[0][1] = float(double(a.m[0][1]) / b); + dst.m[0][2] = float(double(a.m[0][2]) / b); + dst.m[0][3] = float(double(a.m[0][3]) / b); + dst.m[1][0] = float(double(a.m[1][0]) / b); + dst.m[1][1] = float(double(a.m[1][1]) / b); + dst.m[1][2] = float(double(a.m[1][2]) / b); + dst.m[1][3] = float(double(a.m[1][3]) / b); + dst.m[2][0] = float(double(a.m[2][0]) / b); + dst.m[2][1] = float(double(a.m[2][1]) / b); + dst.m[2][2] = float(double(a.m[2][2]) / b); + dst.m[2][3] = float(double(a.m[2][3]) / b); + dst.m[3][0] = float(double(a.m[3][0]) / b); + dst.m[3][1] = float(double(a.m[3][1]) / b); + dst.m[3][2] = float(double(a.m[3][2]) / b); + dst.m[3][3] = float(double(a.m[3][3]) / b); + + return dst; +} + +inline value::matrix4f operator/(const double a, const value::matrix4f &b) { + value::matrix4f dst; + dst.m[0][0] = float(a / double(b.m[0][0])); + dst.m[0][1] = float(a / double(b.m[0][1])); + dst.m[0][2] = float(a / double(b.m[0][2])); + dst.m[0][3] = float(a / double(b.m[0][3])); + dst.m[1][0] = float(a / double(b.m[1][0])); + dst.m[1][1] = float(a / double(b.m[1][1])); + dst.m[1][2] = float(a / double(b.m[1][2])); + dst.m[1][3] = float(a / double(b.m[1][3])); + dst.m[2][0] = float(a / double(b.m[2][0])); + dst.m[2][1] = float(a / double(b.m[2][1])); + dst.m[2][2] = float(a / double(b.m[2][2])); + dst.m[2][3] = float(a / double(b.m[2][3])); + dst.m[3][0] = float(a / double(b.m[3][0])); + dst.m[3][1] = float(a / double(b.m[3][1])); + dst.m[3][2] = float(a / double(b.m[3][2])); + dst.m[3][3] = float(a / double(b.m[3][3])); + + return dst; +} + +inline value::matrix2d operator+(const value::matrix2d &a, const double b) { + value::matrix2d dst; + dst.m[0][0] = a.m[0][0] + b; + dst.m[0][1] = a.m[0][1] + b; + dst.m[1][0] = a.m[1][0] + b; + dst.m[1][1] = a.m[1][1] + b; + + return dst; +} + +inline value::matrix2d operator+(const double a, const value::matrix2d &b) { + value::matrix2d dst; + dst.m[0][0] = a + b.m[0][0]; + dst.m[0][1] = a + b.m[0][1]; + dst.m[1][0] = a + b.m[1][0]; + dst.m[1][1] = a + b.m[1][1]; + + return dst; +} + + +inline value::matrix2d operator-(const value::matrix2d &a, const double b) { + value::matrix2d dst; + dst.m[0][0] = a.m[0][0] - b; + dst.m[0][1] = a.m[0][1] - b; + dst.m[1][0] = a.m[1][0] - b; + dst.m[1][1] = a.m[1][1] - b; + + return dst; +} + +inline value::matrix2d operator-(const double a, const value::matrix2d &b) { + value::matrix2d dst; + dst.m[0][0] = a - b.m[0][0]; + dst.m[0][1] = a - b.m[0][1]; + dst.m[1][0] = a - b.m[1][0]; + dst.m[1][1] = a - b.m[1][1]; + + return dst; +} + +inline value::matrix2d operator*(const value::matrix2d &a, const double b) { + value::matrix2d dst; + dst.m[0][0] = a.m[0][0] * b; + dst.m[0][1] = a.m[0][1] * b; + dst.m[1][0] = a.m[1][0] * b; + dst.m[1][1] = a.m[1][1] * b; + + return dst; +} + +inline value::matrix2d operator*(const double a, const value::matrix2d &b) { + value::matrix2d dst; + dst.m[0][0] = a * b.m[0][0]; + dst.m[0][1] = a * b.m[0][1]; + dst.m[1][0] = a * b.m[1][0]; + dst.m[1][1] = a * b.m[1][1]; + + return dst; +} + +inline value::matrix2d operator/(const value::matrix2d &a, const double b) { + value::matrix2d dst; + dst.m[0][0] = a.m[0][0] / b; + dst.m[0][1] = a.m[0][1] / b; + dst.m[1][0] = a.m[1][0] / b; + dst.m[1][1] = a.m[1][1] / b; + + return dst; +} + +inline value::matrix2d operator/(const double a, const value::matrix2d &b) { + value::matrix2d dst; + dst.m[0][0] = a / b.m[0][0]; + dst.m[0][1] = a / b.m[0][1]; + dst.m[1][0] = a / b.m[1][0]; + dst.m[1][1] = a / b.m[1][1]; + + return dst; +} + +inline value::matrix3d operator+(const value::matrix3d &a, const double b) { + value::matrix3d dst; + dst.m[0][0] = a.m[0][0] + b; + dst.m[0][1] = a.m[0][1] + b; + dst.m[0][2] = a.m[0][2] + b; + dst.m[1][0] = a.m[1][0] + b; + dst.m[1][1] = a.m[1][1] + b; + dst.m[1][2] = a.m[1][2] + b; + dst.m[2][0] = a.m[2][0] + b; + dst.m[2][1] = a.m[2][1] + b; + dst.m[2][2] = a.m[2][2] + b; + + return dst; +} + +inline value::matrix3d operator+(const double a, const value::matrix3d &b) { + value::matrix3d dst; + dst.m[0][0] = a + b.m[0][0]; + dst.m[0][1] = a + b.m[0][1]; + dst.m[0][2] = a + b.m[0][2]; + dst.m[1][0] = a + b.m[1][0]; + dst.m[1][1] = a + b.m[1][1]; + dst.m[1][2] = a + b.m[1][2]; + dst.m[2][0] = a + b.m[2][0]; + dst.m[2][1] = a + b.m[2][1]; + dst.m[2][2] = a + b.m[2][2]; + + return dst; +} + + +inline value::matrix3d operator-(const value::matrix3d &a, const double b) { + value::matrix3d dst; + dst.m[0][0] = a.m[0][0] - b; + dst.m[0][1] = a.m[0][1] - b; + dst.m[0][2] = a.m[0][2] - b; + dst.m[1][0] = a.m[1][0] - b; + dst.m[1][1] = a.m[1][1] - b; + dst.m[1][2] = a.m[1][2] - b; + dst.m[2][0] = a.m[2][0] - b; + dst.m[2][1] = a.m[2][1] - b; + dst.m[2][2] = a.m[2][2] - b; + + return dst; +} + +inline value::matrix3d operator-(const double a, const value::matrix3d &b) { + value::matrix3d dst; + dst.m[0][0] = a - b.m[0][0]; + dst.m[0][1] = a - b.m[0][1]; + dst.m[0][2] = a - b.m[0][2]; + dst.m[1][0] = a - b.m[1][0]; + dst.m[1][1] = a - b.m[1][1]; + dst.m[1][2] = a - b.m[1][2]; + dst.m[2][0] = a - b.m[2][0]; + dst.m[2][1] = a - b.m[2][1]; + dst.m[2][2] = a - b.m[2][2]; + + return dst; +} + +inline value::matrix3d operator*(const value::matrix3d &a, const double b) { + value::matrix3d dst; + dst.m[0][0] = a.m[0][0] * b; + dst.m[0][1] = a.m[0][1] * b; + dst.m[0][2] = a.m[0][2] * b; + dst.m[1][0] = a.m[1][0] * b; + dst.m[1][1] = a.m[1][1] * b; + dst.m[1][2] = a.m[1][2] * b; + dst.m[2][0] = a.m[2][0] * b; + dst.m[2][1] = a.m[2][1] * b; + dst.m[2][2] = a.m[2][2] * b; + + return dst; +} + +inline value::matrix3d operator*(const double a, const value::matrix3d &b) { + value::matrix3d dst; + dst.m[0][0] = a * b.m[0][0]; + dst.m[0][1] = a * b.m[0][1]; + dst.m[0][2] = a * b.m[0][2]; + dst.m[1][0] = a * b.m[1][0]; + dst.m[1][1] = a * b.m[1][1]; + dst.m[1][2] = a * b.m[1][2]; + dst.m[2][0] = a * b.m[2][0]; + dst.m[2][1] = a * b.m[2][1]; + dst.m[2][2] = a * b.m[2][2]; + + return dst; +} + +inline value::matrix3d operator/(const value::matrix3d &a, const double b) { + value::matrix3d dst; + dst.m[0][0] = a.m[0][0] / b; + dst.m[0][1] = a.m[0][1] / b; + dst.m[0][2] = a.m[0][2] / b; + dst.m[1][0] = a.m[1][0] / b; + dst.m[1][1] = a.m[1][1] / b; + dst.m[1][2] = a.m[1][2] / b; + dst.m[2][0] = a.m[2][0] / b; + dst.m[2][1] = a.m[2][1] / b; + dst.m[2][2] = a.m[2][2] / b; + + return dst; +} + +inline value::matrix3d operator/(const double a, const value::matrix3d &b) { + value::matrix3d dst; + dst.m[0][0] = a / b.m[0][0]; + dst.m[0][1] = a / b.m[0][1]; + dst.m[0][2] = a / b.m[0][2]; + dst.m[1][0] = a / b.m[1][0]; + dst.m[1][1] = a / b.m[1][1]; + dst.m[1][2] = a / b.m[1][2]; + dst.m[2][0] = a / b.m[2][0]; + dst.m[2][1] = a / b.m[2][1]; + dst.m[2][2] = a / b.m[2][2]; + + return dst; +} + +inline value::matrix4d operator+(const value::matrix4d &a, const double b) { + value::matrix4d dst; + dst.m[0][0] = a.m[0][0] + b; + dst.m[0][1] = a.m[0][1] + b; + dst.m[0][2] = a.m[0][2] + b; + dst.m[0][3] = a.m[0][3] + b; + dst.m[1][0] = a.m[1][0] + b; + dst.m[1][1] = a.m[1][1] + b; + dst.m[1][2] = a.m[1][2] + b; + dst.m[1][3] = a.m[1][3] + b; + dst.m[2][0] = a.m[2][0] + b; + dst.m[2][1] = a.m[2][1] + b; + dst.m[2][2] = a.m[2][2] + b; + dst.m[2][3] = a.m[2][3] + b; + dst.m[3][0] = a.m[3][0] + b; + dst.m[3][1] = a.m[3][1] + b; + dst.m[3][2] = a.m[3][2] + b; + dst.m[3][3] = a.m[3][3] + b; + + return dst; +} + +inline value::matrix4d operator+(const double a, const value::matrix4d &b) { + value::matrix4d dst; + dst.m[0][0] = a + b.m[0][0]; + dst.m[0][1] = a + b.m[0][1]; + dst.m[0][2] = a + b.m[0][2]; + dst.m[0][3] = a + b.m[0][3]; + dst.m[1][0] = a + b.m[1][0]; + dst.m[1][1] = a + b.m[1][1]; + dst.m[1][2] = a + b.m[1][2]; + dst.m[1][3] = a + b.m[1][3]; + dst.m[2][0] = a + b.m[2][0]; + dst.m[2][1] = a + b.m[2][1]; + dst.m[2][2] = a + b.m[2][2]; + dst.m[2][3] = a + b.m[2][3]; + dst.m[3][0] = a + b.m[3][0]; + dst.m[3][1] = a + b.m[3][1]; + dst.m[3][2] = a + b.m[3][2]; + dst.m[3][3] = a + b.m[3][3]; + + return dst; +} + +inline value::matrix4d operator-(const value::matrix4d &a, const double b) { + value::matrix4d dst; + dst.m[0][0] = a.m[0][0] - b; + dst.m[0][1] = a.m[0][1] - b; + dst.m[0][2] = a.m[0][2] - b; + dst.m[0][3] = a.m[0][3] - b; + dst.m[1][0] = a.m[1][0] - b; + dst.m[1][1] = a.m[1][1] - b; + dst.m[1][2] = a.m[1][2] - b; + dst.m[1][3] = a.m[1][3] - b; + dst.m[2][0] = a.m[2][0] - b; + dst.m[2][1] = a.m[2][1] - b; + dst.m[2][2] = a.m[2][2] - b; + dst.m[2][3] = a.m[2][3] - b; + dst.m[3][0] = a.m[3][0] - b; + dst.m[3][1] = a.m[3][1] - b; + dst.m[3][2] = a.m[3][2] - b; + dst.m[3][3] = a.m[3][3] - b; + + return dst; +} + +inline value::matrix4d operator-(const double a, const value::matrix4d &b) { + value::matrix4d dst; + dst.m[0][0] = a - b.m[0][0]; + dst.m[0][1] = a - b.m[0][1]; + dst.m[0][2] = a - b.m[0][2]; + dst.m[0][3] = a - b.m[0][3]; + dst.m[1][0] = a - b.m[1][0]; + dst.m[1][1] = a - b.m[1][1]; + dst.m[1][2] = a - b.m[1][2]; + dst.m[1][3] = a - b.m[1][3]; + dst.m[2][0] = a - b.m[2][0]; + dst.m[2][1] = a - b.m[2][1]; + dst.m[2][2] = a - b.m[2][2]; + dst.m[2][3] = a - b.m[2][3]; + dst.m[3][0] = a - b.m[3][0]; + dst.m[3][1] = a - b.m[3][1]; + dst.m[3][2] = a - b.m[3][2]; + dst.m[3][3] = a - b.m[3][3]; + + return dst; +} +inline value::matrix4d operator*(const value::matrix4d &a, const double b) { + value::matrix4d dst; + dst.m[0][0] = a.m[0][0] * b; + dst.m[0][1] = a.m[0][1] * b; + dst.m[0][2] = a.m[0][2] * b; + dst.m[0][3] = a.m[0][3] * b; + dst.m[1][0] = a.m[1][0] * b; + dst.m[1][1] = a.m[1][1] * b; + dst.m[1][2] = a.m[1][2] * b; + dst.m[1][3] = a.m[1][3] * b; + dst.m[2][0] = a.m[2][0] * b; + dst.m[2][1] = a.m[2][1] * b; + dst.m[2][2] = a.m[2][2] * b; + dst.m[2][3] = a.m[2][3] * b; + dst.m[3][0] = a.m[3][0] * b; + dst.m[3][1] = a.m[3][1] * b; + dst.m[3][2] = a.m[3][2] * b; + dst.m[3][3] = a.m[3][3] * b; + + return dst; +} + +inline value::matrix4d operator*(const double a, const value::matrix4d &b) { + value::matrix4d dst; + dst.m[0][0] = a * b.m[0][0]; + dst.m[0][1] = a * b.m[0][1]; + dst.m[0][2] = a * b.m[0][2]; + dst.m[0][3] = a * b.m[0][3]; + dst.m[1][0] = a * b.m[1][0]; + dst.m[1][1] = a * b.m[1][1]; + dst.m[1][2] = a * b.m[1][2]; + dst.m[1][3] = a * b.m[1][3]; + dst.m[2][0] = a * b.m[2][0]; + dst.m[2][1] = a * b.m[2][1]; + dst.m[2][2] = a * b.m[2][2]; + dst.m[2][3] = a * b.m[2][3]; + dst.m[3][0] = a * b.m[3][0]; + dst.m[3][1] = a * b.m[3][1]; + dst.m[3][2] = a * b.m[3][2]; + dst.m[3][3] = a * b.m[3][3]; + + return dst; +} + +inline value::matrix4d operator/(const value::matrix4d &a, const double b) { + value::matrix4d dst; + dst.m[0][0] = a.m[0][0] / b; + dst.m[0][1] = a.m[0][1] / b; + dst.m[0][2] = a.m[0][2] / b; + dst.m[0][3] = a.m[0][3] / b; + dst.m[1][0] = a.m[1][0] / b; + dst.m[1][1] = a.m[1][1] / b; + dst.m[1][2] = a.m[1][2] / b; + dst.m[1][3] = a.m[1][3] / b; + dst.m[2][0] = a.m[2][0] / b; + dst.m[2][1] = a.m[2][1] / b; + dst.m[2][2] = a.m[2][2] / b; + dst.m[2][3] = a.m[2][3] / b; + dst.m[3][0] = a.m[3][0] / b; + dst.m[3][1] = a.m[3][1] / b; + dst.m[3][2] = a.m[3][2] / b; + dst.m[3][3] = a.m[3][3] / b; + + return dst; +} + +inline value::matrix4d operator/(const double a, const value::matrix4d &b) { + value::matrix4d dst; + dst.m[0][0] = a / b.m[0][0]; + dst.m[0][1] = a / b.m[0][1]; + dst.m[0][2] = a / b.m[0][2]; + dst.m[0][3] = a / b.m[0][3]; + dst.m[1][0] = a / b.m[1][0]; + dst.m[1][1] = a / b.m[1][1]; + dst.m[1][2] = a / b.m[1][2]; + dst.m[1][3] = a / b.m[1][3]; + dst.m[2][0] = a / b.m[2][0]; + dst.m[2][1] = a / b.m[2][1]; + dst.m[2][2] = a / b.m[2][2]; + dst.m[2][3] = a / b.m[2][3]; + dst.m[3][0] = a / b.m[3][0]; + dst.m[3][1] = a / b.m[3][1]; + dst.m[3][2] = a / b.m[3][2]; + dst.m[3][3] = a / b.m[3][3]; + + return dst; +} + +inline value::frame4d operator+(const value::frame4d &a, const value::frame4d &b) { + value::frame4d dst; + dst.m[0][0] = a.m[0][0] + b.m[0][0]; + dst.m[0][1] = a.m[0][1] + b.m[0][1]; + dst.m[0][2] = a.m[0][2] + b.m[0][2]; + dst.m[0][3] = a.m[0][3] + b.m[0][3]; + dst.m[1][0] = a.m[1][0] + b.m[1][0]; + dst.m[1][1] = a.m[1][1] + b.m[1][1]; + dst.m[1][2] = a.m[1][2] + b.m[1][2]; + dst.m[1][3] = a.m[1][3] + b.m[1][3]; + dst.m[2][0] = a.m[2][0] + b.m[2][0]; + dst.m[2][1] = a.m[2][1] + b.m[2][1]; + dst.m[2][2] = a.m[2][2] + b.m[2][2]; + dst.m[2][3] = a.m[2][3] + b.m[2][3]; + dst.m[3][0] = a.m[3][0] + b.m[3][0]; + dst.m[3][1] = a.m[3][1] + b.m[3][1]; + dst.m[3][2] = a.m[3][2] + b.m[3][2]; + dst.m[3][3] = a.m[3][3] + b.m[3][3]; + + return dst; +} + +inline value::frame4d operator+(const value::frame4d &a, const double b) { + value::frame4d dst; + dst.m[0][0] = a.m[0][0] + b; + dst.m[0][1] = a.m[0][1] + b; + dst.m[0][2] = a.m[0][2] + b; + dst.m[0][3] = a.m[0][3] + b; + dst.m[1][0] = a.m[1][0] + b; + dst.m[1][1] = a.m[1][1] + b; + dst.m[1][2] = a.m[1][2] + b; + dst.m[1][3] = a.m[1][3] + b; + dst.m[2][0] = a.m[2][0] + b; + dst.m[2][1] = a.m[2][1] + b; + dst.m[2][2] = a.m[2][2] + b; + dst.m[2][3] = a.m[2][3] + b; + dst.m[3][0] = a.m[3][0] + b; + dst.m[3][1] = a.m[3][1] + b; + dst.m[3][2] = a.m[3][2] + b; + dst.m[3][3] = a.m[3][3] + b; + + return dst; +} + +inline value::frame4d operator+(const double a, const value::frame4d &b) { + value::frame4d dst; + dst.m[0][0] = a + b.m[0][0]; + dst.m[0][1] = a + b.m[0][1]; + dst.m[0][2] = a + b.m[0][2]; + dst.m[0][3] = a + b.m[0][3]; + dst.m[1][0] = a + b.m[1][0]; + dst.m[1][1] = a + b.m[1][1]; + dst.m[1][2] = a + b.m[1][2]; + dst.m[1][3] = a + b.m[1][3]; + dst.m[2][0] = a + b.m[2][0]; + dst.m[2][1] = a + b.m[2][1]; + dst.m[2][2] = a + b.m[2][2]; + dst.m[2][3] = a + b.m[2][3]; + dst.m[3][0] = a + b.m[3][0]; + dst.m[3][1] = a + b.m[3][1]; + dst.m[3][2] = a + b.m[3][2]; + dst.m[3][3] = a + b.m[3][3]; + + return dst; +} + +inline value::frame4d operator-(const value::frame4d &a, const double b) { + value::frame4d dst; + dst.m[0][0] = a.m[0][0] - b; + dst.m[0][1] = a.m[0][1] - b; + dst.m[0][2] = a.m[0][2] - b; + dst.m[0][3] = a.m[0][3] - b; + dst.m[1][0] = a.m[1][0] - b; + dst.m[1][1] = a.m[1][1] - b; + dst.m[1][2] = a.m[1][2] - b; + dst.m[1][3] = a.m[1][3] - b; + dst.m[2][0] = a.m[2][0] - b; + dst.m[2][1] = a.m[2][1] - b; + dst.m[2][2] = a.m[2][2] - b; + dst.m[2][3] = a.m[2][3] - b; + dst.m[3][0] = a.m[3][0] - b; + dst.m[3][1] = a.m[3][1] - b; + dst.m[3][2] = a.m[3][2] - b; + dst.m[3][3] = a.m[3][3] - b; + + return dst; +} + +inline value::frame4d operator-(const double a, const value::frame4d &b) { + value::frame4d dst; + dst.m[0][0] = a - b.m[0][0]; + dst.m[0][1] = a - b.m[0][1]; + dst.m[0][2] = a - b.m[0][2]; + dst.m[0][3] = a - b.m[0][3]; + dst.m[1][0] = a - b.m[1][0]; + dst.m[1][1] = a - b.m[1][1]; + dst.m[1][2] = a - b.m[1][2]; + dst.m[1][3] = a - b.m[1][3]; + dst.m[2][0] = a - b.m[2][0]; + dst.m[2][1] = a - b.m[2][1]; + dst.m[2][2] = a - b.m[2][2]; + dst.m[2][3] = a - b.m[2][3]; + dst.m[3][0] = a - b.m[3][0]; + dst.m[3][1] = a - b.m[3][1]; + dst.m[3][2] = a - b.m[3][2]; + dst.m[3][3] = a - b.m[3][3]; + + return dst; +} +inline value::frame4d operator*(const value::frame4d &a, const double b) { + value::frame4d dst; + dst.m[0][0] = a.m[0][0] * b; + dst.m[0][1] = a.m[0][1] * b; + dst.m[0][2] = a.m[0][2] * b; + dst.m[0][3] = a.m[0][3] * b; + dst.m[1][0] = a.m[1][0] * b; + dst.m[1][1] = a.m[1][1] * b; + dst.m[1][2] = a.m[1][2] * b; + dst.m[1][3] = a.m[1][3] * b; + dst.m[2][0] = a.m[2][0] * b; + dst.m[2][1] = a.m[2][1] * b; + dst.m[2][2] = a.m[2][2] * b; + dst.m[2][3] = a.m[2][3] * b; + dst.m[3][0] = a.m[3][0] * b; + dst.m[3][1] = a.m[3][1] * b; + dst.m[3][2] = a.m[3][2] * b; + dst.m[3][3] = a.m[3][3] * b; + + return dst; +} + +inline value::frame4d operator*(const double a, const value::frame4d &b) { + value::frame4d dst; + dst.m[0][0] = a * b.m[0][0]; + dst.m[0][1] = a * b.m[0][1]; + dst.m[0][2] = a * b.m[0][2]; + dst.m[0][3] = a * b.m[0][3]; + dst.m[1][0] = a * b.m[1][0]; + dst.m[1][1] = a * b.m[1][1]; + dst.m[1][2] = a * b.m[1][2]; + dst.m[1][3] = a * b.m[1][3]; + dst.m[2][0] = a * b.m[2][0]; + dst.m[2][1] = a * b.m[2][1]; + dst.m[2][2] = a * b.m[2][2]; + dst.m[2][3] = a * b.m[2][3]; + dst.m[3][0] = a * b.m[3][0]; + dst.m[3][1] = a * b.m[3][1]; + dst.m[3][2] = a * b.m[3][2]; + dst.m[3][3] = a * b.m[3][3]; + + return dst; +} + +inline value::frame4d operator/(const value::frame4d &a, const double b) { + value::frame4d dst; + dst.m[0][0] = a.m[0][0] / b; + dst.m[0][1] = a.m[0][1] / b; + dst.m[0][2] = a.m[0][2] / b; + dst.m[0][3] = a.m[0][3] / b; + dst.m[1][0] = a.m[1][0] / b; + dst.m[1][1] = a.m[1][1] / b; + dst.m[1][2] = a.m[1][2] / b; + dst.m[1][3] = a.m[1][3] / b; + dst.m[2][0] = a.m[2][0] / b; + dst.m[2][1] = a.m[2][1] / b; + dst.m[2][2] = a.m[2][2] / b; + dst.m[2][3] = a.m[2][3] / b; + dst.m[3][0] = a.m[3][0] / b; + dst.m[3][1] = a.m[3][1] / b; + dst.m[3][2] = a.m[3][2] / b; + dst.m[3][3] = a.m[3][3] / b; + + return dst; +} + +inline value::frame4d operator/(const double a, const value::frame4d &b) { + value::frame4d dst; + dst.m[0][0] = a / b.m[0][0]; + dst.m[0][1] = a / b.m[0][1]; + dst.m[0][2] = a / b.m[0][2]; + dst.m[0][3] = a / b.m[0][3]; + dst.m[1][0] = a / b.m[1][0]; + dst.m[1][1] = a / b.m[1][1]; + dst.m[1][2] = a / b.m[1][2]; + dst.m[1][3] = a / b.m[1][3]; + dst.m[2][0] = a / b.m[2][0]; + dst.m[2][1] = a / b.m[2][1]; + dst.m[2][2] = a / b.m[2][2]; + dst.m[2][3] = a / b.m[2][3]; + dst.m[3][0] = a / b.m[3][0]; + dst.m[3][1] = a / b.m[3][1]; + dst.m[3][2] = a / b.m[3][2]; + dst.m[3][3] = a / b.m[3][3]; + + return dst; +} + +// half + +#if 0 +inline value::half2 operator+(const value::half2 &a, const value::half2 &b) { + return {a[0] + b[0], a[1] + b[1]}; +} + +inline value::half2 operator+(const float a, const value::half2 &b) { + return {a + b[0], a + b[1]}; +} + +inline value::half2 operator+(const value::half2 &a, const float b) { + return {a[0] + b, a[1] + b}; +} + +inline value::half2 operator-(const value::half2 &a, const value::half2 &b) { + return {a[0] - b[0], a[1] - b[1]}; +} + +inline value::half2 operator-(const float a, const value::half2 &b) { + return {a - b[0], a - b[1]}; +} + +inline value::half2 operator-(const value::half2 &a, const float b) { + return {a[0] - b, a[1] - b}; +} + +inline value::half2 operator*(const value::half2 &a, const value::half2 &b) { + return {a[0] * b[0], a[1] * b[1]}; +} + +inline value::half2 operator*(const float a, const value::half2 &b) { + return {a * b[0], a * b[1]}; +} + +inline value::half2 operator*(const value::half2 &a, const float b) { + return {a[0] * b, a[1] * b}; +} + +inline value::half2 operator/(const value::half2 &a, const value::half2 &b) { + return {a[0] / b[0], a[1] / b[1]}; +} + +inline value::half2 operator/(const float a, const value::half2 &b) { + return {a / b[0], a / b[1]}; +} + +inline value::half2 operator/(const value::half2 &a, const float b) { + return {a[0] / b, a[1] / b}; +} + +inline value::half3 operator+(const value::half3 &a, const value::half3 &b) { + return {a[0] + b[0], a[1] + b[1], a[2] + b[2]}; +} + +inline value::half3 operator+(const float a, const value::half3 &b) { + return {a + b[0], a + b[1], a + b[2]}; +} + +inline value::half3 operator+(const value::half3 &a, const float b) { + return {a[0] + b, a[1] + b, a[2] + b}; +} + +inline value::half3 operator*(const value::half3 &a, const value::half3 &b) { + return {a[0] * b[0], a[1] * b[1], a[2] * b[2]}; +} + +inline value::half3 operator*(const float a, const value::half3 &b) { + return {a * b[0], a * b[1], a * b[2]}; +} + +inline value::half3 operator*(const value::half3 &a, const float b) { + return {a[0] * b, a[1] * b, a[2] * b}; +} + +inline value::half3 operator/(const value::half3 &a, const value::half3 &b) { + return {a[0] / b[0], a[1] / b[1], a[2] / b[2]}; +} + +inline value::half3 operator/(const float a, const value::half3 &b) { + return {a / b[0], a / b[1], a / b[2]}; +} + +inline value::half3 operator/(const value::half3 &a, const float b) { + return {a[0] / b, a[1] / b, a[2] / b}; +} + +inline value::half4 operator+(const value::half4 &a, const value::half4 &b) { + return {a[0] + b[0], a[1] + b[1], a[2] - b[2], a[3] - b[3]}; +} + +inline value::half4 operator+(const float a, const value::half4 &b) { + return {a + b[0], a + b[1], a + b[2], a + b[3]}; +} + +inline value::half4 operator+(const value::half4 &a, const float b) { + return {a[0] + b, a[1] + b, a[2] + b, a[3] + b}; +} + +inline value::half4 operator-(const value::half4 &a, const value::half4 &b) { + return {a[0] - b[0], a[1] - b[1], a[2] - b[2], a[3] - b[3]}; +} + +inline value::half4 operator-(const float a, const value::half4 &b) { + return {a - b[0], a - b[1], a - b[2], a - b[3]}; +} + +inline value::half4 operator-(const value::half4 &a, const float b) { + return {a[0] - b, a[1] - b, a[2] - b, a[3] - b}; +} + +inline value::half4 operator*(const value::half4 &a, const value::half4 &b) { + return {a[0] * b[0], a[1] * b[1], a[2] * b[2], a[3] * b[3]}; +} + +inline value::half4 operator*(const float a, const value::half4 &b) { + return {a * b[0], a * b[1], a * b[2], a * b[3]}; +} + +inline value::half4 operator*(const value::half4 &a, const float b) { + + return {a[0] * b, a[1] * b, a[2] * b, a[3] * b}; +} + +inline value::half4 operator/(const value::half4 &a, const value::half4 &b) { + return {a[0] / b[0], a[1] / b[1], a[2] / b[2], a[3] / b[3]}; +} + +inline value::half4 operator/(const float a, const value::half4 &b) { + return {a / b[0], a / b[1], a / b[2], a / b[3]}; +} + +inline value::half4 operator/(const value::half4 &a, const float b) { + + return {a[0] / b, a[1] / b, a[2] / b, a[3] / b}; +} + +// float + +inline value::float2 operator+(const value::float2 &a, const value::float2 &b) { + return {a[0] + b[0], a[1] + b[1]}; +} + +inline value::float2 operator+(const float a, const value::float2 &b) { + return {a + b[0], a + b[1]}; +} + +inline value::float2 operator+(const value::float2 &a, const float b) { + return {a[0] + b, a[1] + b}; +} + + +inline value::float2 operator-(const value::float2 &a, const value::float2 &b) { + return {a[0] - b[0], a[1] - b[1]}; +} + +inline value::float2 operator-(const float a, const value::float2 &b) { + return {a - b[0], a - b[1]}; +} + +inline value::float2 operator-(const value::float2 &a, const float b) { + return {a[0] - b, a[1] - b}; +} + +inline value::float2 operator*(const value::float2 &a, const value::float2 &b) { + return {a[0] * b[0], a[1] * b[1]}; +} + +inline value::float2 operator*(const float a, const value::float2 &b) { + return {a * b[0], a * b[1]}; +} + +inline value::float2 operator*(const value::float2 &a, const float b) { + return {a[0] * b, a[1] * b}; +} + +inline value::float2 operator/(const value::float2 &a, const value::float2 &b) { + return {a[0] / b[0], a[1] / b[1]}; +} + +inline value::float2 operator/(const float a, const value::float2 &b) { + return {a / b[0], a / b[1]}; +} + +inline value::float2 operator/(const value::float2 &a, const float b) { + return {a[0] / b, a[1] / b}; +} + +inline value::float3 operator+(const value::float3 &a, const value::float3 &b) { + return {a[0] + b[0], a[1] + b[1], a[2] + b[2]}; +} + +inline value::float3 operator+(const float a, const value::float3 &b) { + return {a + b[0], a + b[1], a + b[2]}; +} + +inline value::float3 operator+(const value::float3 &a, const float b) { + return {a[0] + b, a[1] + b, a[2] + b}; +} + +inline value::float3 operator-(const value::float3 &a, const value::float3 &b) { + return {a[0] - b[0], a[1] - b[1], a[2] - b[2]}; +} + +inline value::float3 operator-(const float a, const value::float3 &b) { + return {a - b[0], a - b[1], a - b[2]}; +} + +inline value::float3 operator-(const value::float3 &a, const float b) { + return {a[0] - b, a[1] - b, a[2] - b}; +} + +inline value::float3 operator*(const value::float3 &a, const value::float3 &b) { + return {a[0] * b[0], a[1] * b[1], a[2] * b[2]}; +} + +inline value::float3 operator*(const float a, const value::float3 &b) { + return {a * b[0], a * b[1], a * b[2]}; +} + +inline value::float3 operator*(const value::float3 &a, const float b) { + return {a[0] * b, a[1] * b, a[2] * b}; +} + +inline value::float3 operator/(const value::float3 &a, const value::float3 &b) { + return {a[0] / b[0], a[1] / b[1], a[2] / b[2]}; +} + +inline value::float3 operator/(const float a, const value::float3 &b) { + return {a / b[0], a / b[1], a / b[2]}; +} + +inline value::float3 operator/(const value::float3 &a, const float b) { + return {a[0] / b, a[1] / b, a[2] / b}; +} + +inline value::float4 operator+(const value::float4 &a, const value::float4 &b) { + return {a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]}; +} + +inline value::float4 operator+(const float a, const value::float4 &b) { + return {a + b[0], a + b[1], a + b[2], a + b[3]}; +} + +inline value::float4 operator+(const value::float4 &a, const float b) { + return {a[0] + b, a[1] + b, a[2] + b, a[3] + b}; +} + +inline value::float4 operator-(const value::float4 &a, const value::float4 &b) { + return {a[0] - b[0], a[1] - b[1], a[2] - b[2], a[3] - b[3]}; +} + +inline value::float4 operator-(const float a, const value::float4 &b) { + return {a - b[0], a - b[1], a - b[2], a - b[3]}; +} + +inline value::float4 operator-(const value::float4 &a, const float b) { + return {a[0] - b, a[1] - b, a[2] - b, a[3] - b}; +} + + + +inline value::float4 operator*(const value::float4 &a, const value::float4 &b) { + return {a[0] * b[0], a[1] * b[1], a[2] * b[2], a[3] * b[3]}; +} + +inline value::float4 operator*(const float a, const value::float4 &b) { + return {a * b[0], a * b[1], a * b[2], a * b[3]}; +} + +inline value::float4 operator*(const value::float4 &a, const float b) { + + return {a[0] * b, a[1] * b, a[2] * b, a[3] * b}; +} + +// double + +inline value::double2 operator+(const value::double2 &a, const value::double2 &b) { + return {a[0] + b[0], a[1] + b[1]}; +} + +inline value::double2 operator+(const double a, const value::double2 &b) { + return {a + b[0], a + b[1]}; +} + +inline value::double2 operator+(const value::double2 &a, const double b) { + return {a[0] + b, a[1] + b}; +} + + +inline value::double2 operator-(const value::double2 &a, const value::double2 &b) { + return {a[0] - b[0], a[1] - b[1]}; +} + +inline value::double2 operator-(const double a, const value::double2 &b) { + return {a - b[0], a - b[1]}; +} + +inline value::double2 operator-(const value::double2 &a, const double b) { + return {a[0] - b, a[1] - b}; +} + +inline value::double2 operator*(const value::double2 &a, const value::double2 &b) { + return {a[0] * b[0], a[1] * b[1]}; +} + +inline value::double2 operator*(const double a, const value::double2 &b) { + return {a * b[0], a * b[1]}; +} + +inline value::double2 operator*(const value::double2 &a, const double b) { + return {a[0] * b, a[1] * b}; +} + +inline value::double2 operator/(const value::double2 &a, const value::double2 &b) { + return {a[0] / b[0], a[1] / b[1]}; +} + +inline value::double2 operator/(const double a, const value::double2 &b) { + return {a / b[0], a / b[1]}; +} + +inline value::double2 operator/(const value::double2 &a, const double b) { + return {a[0] / b, a[1] / b}; +} + +inline value::double3 operator+(const value::double3 &a, const value::double3 &b) { + return {a[0] + b[0], a[1] + b[1], a[2] + b[2]}; +} + +inline value::double3 operator+(const double a, const value::double3 &b) { + return {a + b[0], a + b[1], a + b[2]}; +} + +inline value::double3 operator+(const value::double3 &a, const double b) { + return {a[0] + b, a[1] + b, a[2] + b}; +} + +inline value::double3 operator-(const value::double3 &a, const value::double3 &b) { + return {a[0] - b[0], a[1] - b[1], a[2] - b[2]}; +} + +inline value::double3 operator-(const double a, const value::double3 &b) { + return {a - b[0], a - b[1], a - b[2]}; +} + +inline value::double3 operator-(const value::double3 &a, const double b) { + return {a[0] - b, a[1] - b, a[2] - b}; +} + +inline value::double3 operator*(const value::double3 &a, const value::double3 &b) { + return {a[0] * b[0], a[1] * b[1], a[2] * b[2]}; +} + +inline value::double3 operator*(const double a, const value::double3 &b) { + return {a * b[0], a * b[1], a * b[2]}; +} + +inline value::double3 operator*(const value::double3 &a, const double b) { + return {a[0] * b, a[1] * b, a[2] * b}; +} + +inline value::double3 operator/(const value::double3 &a, const value::double3 &b) { + return {a[0] / b[0], a[1] / b[1], a[2] / b[2]}; +} + +inline value::double3 operator/(const double a, const value::double3 &b) { + return {a / b[0], a / b[1], a / b[2]}; +} + +inline value::double3 operator/(const value::double3 &a, const double b) { + return {a[0] / b, a[1] / b, a[2] / b}; +} + +inline value::double4 operator+(const value::double4 &a, const value::double4 &b) { + return {a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]}; +} + +inline value::double4 operator+(const double a, const value::double4 &b) { + return {a + b[0], a + b[1], a + b[2], a + b[3]}; +} + +inline value::double4 operator+(const value::double4 &a, const double b) { + return {a[0] + b, a[1] + b, a[2] + b, a[3] + b}; +} + +inline value::double4 operator-(const value::double4 &a, const value::double4 &b) { + return {a[0] - b[0], a[1] - b[1], a[2] - b[2], a[3] - b[3]}; +} + +inline value::double4 operator-(const double a, const value::double4 &b) { + return {a - b[0], a - b[1], a - b[2], a - b[3]}; +} + +inline value::double4 operator-(const value::double4 &a, const double b) { + return {a[0] - b, a[1] - b, a[2] - b, a[3] - b}; +} + + + +inline value::double4 operator*(const value::double4 &a, const value::double4 &b) { + return {a[0] * b[0], a[1] * b[1], a[2] * b[2], a[3] * b[3]}; +} + +inline value::double4 operator*(const double a, const value::double4 &b) { + return {a * b[0], a * b[1], a * b[2], a * b[3]}; +} + +inline value::double4 operator*(const value::double4 &a, const double b) { + + return {a[0] * b, a[1] * b, a[2] * b, a[3] * b}; +} + +// normal +inline value::normal3f operator+(const value::normal3f &a, const value::normal3f &b) { + return {a[0] + b[0], a[1] + b[1], a[2] + b[2]}; +} + +inline value::normal3f operator+(const float a, const value::normal3f &b) { + return {a + b[0], a + b[1], a + b[2]}; +} + +inline value::normal3f operator+(const value::normal3f &a, const float b) { + return {a[0] + b, a[1] + b, a[2] + b}; +} + +inline value::normal3f operator-(const value::normal3f &a, const value::normal3f &b) { + return {a[0] - b[0], a[1] - b[1], a[2] - b[2]}; +} + +inline value::normal3f operator-(const float a, const value::normal3f &b) { + return {a - b[0], a - b[1], a - b[2]}; +} + +inline value::normal3f operator-(const value::normal3f &a, const float b) { + return {a[0] - b, a[1] - b, a[2] - b}; +} + +inline value::normal3f operator*(const value::normal3f &a, const value::normal3f &b) { + return {a[0] * b[0], a[1] * b[1], a[2] * b[2]}; +} + +inline value::normal3f operator*(const float a, const value::normal3f &b) { + return {a * b[0], a * b[1], a * b[2]}; +} + +inline value::normal3f operator*(const value::normal3f &a, const float b) { + return {a[0] * b, a[1] * b, a[2] * b}; +} + +inline value::normal3f operator/(const value::normal3f &a, const value::normal3f &b) { + return {a[0] / b[0], a[1] / b[1], a[2] / b[2]}; +} + +inline value::normal3f operator/(const float a, const value::normal3f &b) { + return {a / b[0], a / b[1], a / b[2]}; +} + +inline value::normal3f operator/(const value::normal3f &a, const float b) { + return {a[0] / b, a[1] / b, a[2] / b}; +} + +// normal +inline value::normal3d operator+(const value::normal3d &a, const value::normal3d &b) { + return {a[0] + b[0], a[1] + b[1], a[2] + b[2]}; +} + +inline value::normal3d operator+(const double a, const value::normal3d &b) { + return {a + b[0], a + b[1], a + b[2]}; +} + +inline value::normal3d operator+(const value::normal3d &a, const double b) { + return {a[0] + b, a[1] + b, a[2] + b}; +} + +inline value::normal3d operator-(const value::normal3d &a, const value::normal3d &b) { + return {a[0] - b[0], a[1] - b[1], a[2] - b[2]}; +} + +inline value::normal3d operator-(const double a, const value::normal3d &b) { + return {a - b[0], a - b[1], a - b[2]}; +} + +inline value::normal3d operator-(const value::normal3d &a, const double b) { + return {a[0] - b, a[1] - b, a[2] - b}; +} + +inline value::normal3d operator*(const value::normal3d &a, const value::normal3d &b) { + return {a[0] * b[0], a[1] * b[1], a[2] * b[2]}; +} + +inline value::normal3d operator*(const double a, const value::normal3d &b) { + return {a * b[0], a * b[1], a * b[2]}; +} + +inline value::normal3d operator*(const value::normal3d &a, const double b) { + return {a[0] * b, a[1] * b, a[2] * b}; +} + +inline value::normal3d operator/(const value::normal3d &a, const value::normal3d &b) { + return {a[0] / b[0], a[1] / b[1], a[2] / b[2]}; +} + +inline value::normal3d operator/(const double a, const value::normal3d &b) { + return {a / b[0], a / b[1], a / b[2]}; +} + +inline value::normal3d operator/(const value::normal3d &a, const double b) { + return {a[0] / b, a[1] / b, a[2] / b}; +} + +// vector +inline value::vector3f operator+(const value::vector3f &a, const value::vector3f &b) { + return {a[0] + b[0], a[1] + b[1], a[2] + b[2]}; +} + +inline value::vector3f operator+(const float a, const value::vector3f &b) { + return {a + b[0], a + b[1], a + b[2]}; +} + +inline value::vector3f operator+(const value::vector3f &a, const float b) { + return {a[0] + b, a[1] + b, a[2] + b}; +} + +inline value::vector3f operator-(const value::vector3f &a, const value::vector3f &b) { + return {a[0] - b[0], a[1] - b[1], a[2] - b[2]}; +} + +inline value::vector3f operator-(const float a, const value::vector3f &b) { + return {a - b[0], a - b[1], a - b[2]}; +} + +inline value::vector3f operator-(const value::vector3f &a, const float b) { + return {a[0] - b, a[1] - b, a[2] - b}; +} + +inline value::vector3f operator*(const value::vector3f &a, const value::vector3f &b) { + return {a[0] * b[0], a[1] * b[1], a[2] * b[2]}; +} + +inline value::vector3f operator*(const float a, const value::vector3f &b) { + return {a * b[0], a * b[1], a * b[2]}; +} + +inline value::vector3f operator*(const value::vector3f &a, const float b) { + return {a[0] * b, a[1] * b, a[2] * b}; +} + +inline value::vector3f operator/(const value::vector3f &a, const value::vector3f &b) { + return {a[0] / b[0], a[1] / b[1], a[2] / b[2]}; +} + +inline value::vector3f operator/(const float a, const value::vector3f &b) { + return {a / b[0], a / b[1], a / b[2]}; +} + +inline value::vector3f operator/(const value::vector3f &a, const float b) { + return {a[0] / b, a[1] / b, a[2] / b}; +} +#endif + +// +// Inlined lerp +// (Use prim-types.hh::Lerp if you do not need the performance) + +// no lerp by default +template +inline T lerp(const T &a, const T &b, const double t) { + (void)b; + (void)t; + return a; +} + +#define TUSD_INLINED_LERP(__ty, __interp_ty) \ +template <> \ +inline __ty lerp(const __ty &a, const __ty &b, const double t) { \ + return __interp_ty(1.0 - t) * a + __interp_ty(t) * b; \ +} + +TUSD_INLINED_LERP(value::half, float) +TUSD_INLINED_LERP(value::half2, float) +TUSD_INLINED_LERP(value::half3, float) +TUSD_INLINED_LERP(value::half4, float) +TUSD_INLINED_LERP(float, float) +TUSD_INLINED_LERP(value::float2, float) +TUSD_INLINED_LERP(value::float3, float) +TUSD_INLINED_LERP(value::float4, float) +TUSD_INLINED_LERP(double, double) +TUSD_INLINED_LERP(value::double2, double) +TUSD_INLINED_LERP(value::double3, double) +TUSD_INLINED_LERP(value::double4, double) +TUSD_INLINED_LERP(value::normal3h, float) +TUSD_INLINED_LERP(value::normal3f, float) +TUSD_INLINED_LERP(value::normal3d, double) +TUSD_INLINED_LERP(value::vector3h, float) +TUSD_INLINED_LERP(value::vector3f, float) +TUSD_INLINED_LERP(value::vector3d, double) +TUSD_INLINED_LERP(value::point3h, float) +TUSD_INLINED_LERP(value::point3f, float) +TUSD_INLINED_LERP(value::point3d, double) +TUSD_INLINED_LERP(value::color3h, float) +TUSD_INLINED_LERP(value::color3f, float) +TUSD_INLINED_LERP(value::color3d, double) +TUSD_INLINED_LERP(value::color4h, float) +TUSD_INLINED_LERP(value::color4f, float) +TUSD_INLINED_LERP(value::color4d, double) +TUSD_INLINED_LERP(value::texcoord2h, float) +TUSD_INLINED_LERP(value::texcoord2f, float) +TUSD_INLINED_LERP(value::texcoord2d, double) +TUSD_INLINED_LERP(value::texcoord3h, float) +TUSD_INLINED_LERP(value::texcoord3f, float) +TUSD_INLINED_LERP(value::texcoord3d, double) + +// TODO: robust arithmetic for matrix add/sub/mul/div +TUSD_INLINED_LERP(value::matrix2f, double) +TUSD_INLINED_LERP(value::matrix3f, double) +TUSD_INLINED_LERP(value::matrix4f, double) +TUSD_INLINED_LERP(value::matrix2d, double) +TUSD_INLINED_LERP(value::matrix3d, double) +TUSD_INLINED_LERP(value::matrix4d, double) +TUSD_INLINED_LERP(value::frame4d, double) +//TUSD_INLINED_LERP(value::timecode, double) + +#undef TUSD_INLINED_LERP + + + +// for generic vector data. +template +inline std::vector lerp(const std::vector &a, const std::vector &b, + const double t) { + std::vector dst; + + // Choose shorter one + size_t n = std::min(a.size(), b.size()); + if (n == 0) { + return dst; + } + + dst.resize(n); + + if (a.size() != b.size()) { + return dst; + } + for (size_t i = 0; i < n; i++) { + dst[i] = lerp(a[i], b[i], t); + } + + return dst; +} + +template <> +inline value::quath lerp(const value::quath &a, const value::quath &b, const double t) { + // to float. + value::quatf af; + value::quatf bf; + af.real = half_to_float(a.real); + af.imag[0] = half_to_float(a.imag[0]); + af.imag[1] = half_to_float(a.imag[1]); + af.imag[2] = half_to_float(a.imag[2]); + + bf.real = half_to_float(b.real); + bf.imag[0] = half_to_float(b.imag[0]); + bf.imag[1] = half_to_float(b.imag[1]); + bf.imag[2] = half_to_float(b.imag[2]); + + value::quatf ret = slerp(af, bf, float(t)); + value::quath h; + h.real = value::float_to_half_full(ret.real); + h.imag[0] = value::float_to_half_full(ret.imag[0]); + h.imag[1] = value::float_to_half_full(ret.imag[1]); + h.imag[2] = value::float_to_half_full(ret.imag[2]); + + return h; +} + +template <> +inline value::quatf lerp(const value::quatf &a, const value::quatf &b, const double t) { + // slerp + return slerp(a, b, float(t)); +} + +template <> +inline value::quatd lerp(const value::quatd &a, const value::quatd &b, const double t) { + // slerp + return slerp(a, b, t); +} + +#if 0 +// specializations for non-lerp-able types +template <> +inline value::AssetPath lerp(const value::AssetPath &a, + const value::AssetPath &b, const double t) { + (void)b; + (void)t; + // no interpolation + return a; +} + +template <> +inline std::vector lerp( + const std::vector &a, + const std::vector &b, const double t) { + (void)b; + (void)t; + // no interpolation + return a; +} +#endif + + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/value-pprint.cc b/contrib/tinyusdz/tinyusdz_repo/src/value-pprint.cc new file mode 100644 index 000000000..f429589fc --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/value-pprint.cc @@ -0,0 +1,1027 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. + +#include "value-pprint.hh" + +#include + +#include "pprinter.hh" +#include "prim-types.hh" +#include "str-util.hh" +#include "usdGeom.hh" +#include "usdLux.hh" +#include "value-types.hh" + +// +#include "common-macros.inc" + +// For fast int/float to ascii +// Default disabled. +//#define TINYUSDZ_LOCAL_USE_JEAIII_ITOA + +#if defined(TINYUSDZ_LOCAL_USE_JEAIII_ITOA) +#include "external/jeaiii_to_text.h" +#endif + +// dtoa_milo does not work well for float types +// (e.g. it prints float 0.01 as 0.009999999997), +// so use floaxie for float types +// TODO: Use floaxie also for double? +#include "external/dtoa_milo.h" + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#include "external/floaxie/floaxie/ftoa.h" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace tinyusdz { + +namespace { + +#if defined(TINYUSDZ_LOCAL_USE_JEAIII_ITOA) +void itoa(uint32_t n, char* b) { *jeaiii::to_text_from_integer(b, n) = '\0'; } +void itoa(int32_t n, char* b) { *jeaiii::to_text_from_integer(b, n) = '\0'; } +void itoa(uint64_t n, char* b) { *jeaiii::to_text_from_integer(b, n) = '\0'; } +void itoa(int64_t n, char* b) { *jeaiii::to_text_from_integer(b, n) = '\0'; } +#endif + +inline std::string dtos(const float v) { + + char buf[floaxie::max_buffer_size()]; + size_t n = floaxie::ftoa(v, buf); + + return std::string(buf, buf + n); +} + +inline std::string dtos(const double v) { + + char buf[128]; + dtoa_milo(v, buf); + + return std::string(buf); +} + +} // local + +} // namespace tinyusdz + +namespace std { + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::half &v) { + os << tinyusdz::value::half_to_float(v); + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::half2 &v) { + os << "(" << v[0] << ", " << v[1] << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::half3 &v) { + os << "(" << v[0] << ", " << v[1] << ", " << v[2] << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::half4 &v) { + os << "(" << v[0] << ", " << v[1] << ", " << v[2] << ", " << v[3] << ")"; + return os; +} + +// treat char vector type as byte +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::char2 &v) { + os << "(" << int(v[0]) << ", " << int(v[1]) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::char3 &v) { + os << "(" << int(v[0]) << ", " << int(v[1]) << ", " << int(v[2]) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::char4 &v) { + os << "(" << int(v[0]) << ", " << int(v[1]) << ", " << int(v[2]) << ", " << int(v[3]) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::uchar2 &v) { + os << "(" << int(v[0]) << ", " << int(v[1]) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::uchar3 &v) { + os << "(" << int(v[0]) << ", " << int(v[1]) << ", " << int(v[2]) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::uchar4 &v) { + os << "(" << int(v[0]) << ", " << int(v[1]) << ", " << int(v[2]) << ", " << int(v[3]) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::short2 &v) { + os << "(" << v[0] << ", " << v[1] << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::short3 &v) { + os << "(" << v[0] << ", " << v[1] << ", " << v[2] << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::short4 &v) { + os << "(" << v[0] << ", " << v[1] << ", " << v[2] << ", " << v[3] << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::ushort2 &v) { + os << "(" << v[0] << ", " << v[1] << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::ushort3 &v) { + os << "(" << v[0] << ", " << v[1] << ", " << v[2] << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::ushort4 &v) { + os << "(" << v[0] << ", " << v[1] << ", " << v[2] << ", " << v[3] << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::int2 &v) { + os << "(" << v[0] << ", " << v[1] << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::int3 &v) { + os << "(" << v[0] << ", " << v[1] << ", " << v[2] << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::int4 &v) { + os << "(" << v[0] << ", " << v[1] << ", " << v[2] << ", " << v[3] << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::uint2 &v) { + os << "(" << v[0] << ", " << v[1] << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::uint3 &v) { + os << "(" << v[0] << ", " << v[1] << ", " << v[2] << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::uint4 &v) { + os << "(" << v[0] << ", " << v[1] << ", " << v[2] << ", " << v[3] << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::float2 &v) { + os << "(" << tinyusdz::dtos(v[0]) << ", " << tinyusdz::dtos(v[1]) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::float3 &v) { + os << "(" << tinyusdz::dtos(v[0]) << ", " << tinyusdz::dtos(v[1]) << ", " << tinyusdz::dtos(v[2]) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::float4 &v) { + os << "(" << tinyusdz::dtos(v[0]) << ", " << tinyusdz::dtos(v[1]) << ", " << tinyusdz::dtos(v[2]) << ", " << tinyusdz::dtos(v[3]) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::double2 &v) { + os << "(" << tinyusdz::dtos(v[0]) << ", " << tinyusdz::dtos(v[1]) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::double3 &v) { + os << "(" << tinyusdz::dtos(v[0]) << ", " << tinyusdz::dtos(v[1]) << ", " << tinyusdz::dtos(v[2]) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::double4 &v) { + os << "(" << tinyusdz::dtos(v[0]) << ", " << tinyusdz::dtos(v[1]) << ", " << tinyusdz::dtos(v[2]) << ", " << tinyusdz::dtos(v[3]) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::vector3h &v) { + os << "(" << v.x << ", " << v.y << ", " << v.z << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::vector3f &v) { + os << "(" << tinyusdz::dtos(v.x) << ", " << tinyusdz::dtos(v.y) << ", " << tinyusdz::dtos(v.z) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::vector3d &v) { + os << "(" << tinyusdz::dtos(v.x) << ", " << tinyusdz::dtos(v.y) << ", " << tinyusdz::dtos(v.z) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::normal3h &v) { + os << "(" << v.x << ", " << v.y << ", " << v.z << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::normal3f &v) { + os << "(" << tinyusdz::dtos(v.x) << ", " << tinyusdz::dtos(v.y) << ", " << tinyusdz::dtos(v.z) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::normal3d &v) { + os << "(" << tinyusdz::dtos(v.x) << ", " << tinyusdz::dtos(v.y) << ", " << tinyusdz::dtos(v.z) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::point3h &v) { + os << "(" << v.x << ", " << v.y << ", " << v.z << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::point3f &v) { + os << "(" << tinyusdz::dtos(v.x) << ", " << tinyusdz::dtos(v.y) << ", " << tinyusdz::dtos(v.z) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::point3d &v) { + os << "(" << tinyusdz::dtos(v.x) << ", " << tinyusdz::dtos(v.y) << ", " << tinyusdz::dtos(v.z) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::color3h &v) { + os << "(" << tinyusdz::value::half_to_float(v.r) << ", " + << tinyusdz::value::half_to_float(v.g) << ", " << tinyusdz::value::half_to_float(v.b) + << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::color3f &v) { + os << "(" << tinyusdz::dtos(v.r) << ", " << tinyusdz::dtos(v.g) << ", " << tinyusdz::dtos(v.b) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::color3d &v) { + os << "(" << tinyusdz::dtos(v.r) << ", " << tinyusdz::dtos(v.g) << ", " << tinyusdz::dtos(v.b) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::color4h &v) { + os << "(" << tinyusdz::value::half_to_float(v.r) << ", " + << tinyusdz::value::half_to_float(v.g) << ", " << tinyusdz::value::half_to_float(v.b) + << ", " << tinyusdz::value::half_to_float(v.a) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::color4f &v) { + os << "(" << tinyusdz::dtos(v.r) << ", " << tinyusdz::dtos(v.g) << ", " << tinyusdz::dtos(v.b) << ", " << tinyusdz::dtos(v.a) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::color4d &v) { + os << "(" << tinyusdz::dtos(v.r) << ", " << tinyusdz::dtos(v.g) << ", " << tinyusdz::dtos(v.b) << ", " << tinyusdz::dtos(v.a) << ")"; + return os; +} + +// pxrUSD prints quateron in [w, x, y, z] order +// https://github.com/PixarAnimationStudios/USD/blob/3abc46452b1271df7650e9948fef9f0ce602e3b2/pxr/base/gf/quatf.h#L287 +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::quath &v) { + os << "(" << v.real << ", " << v.imag[0] << ", " << v.imag[1] << ", " + << v.imag[2] << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::quatf &v) { + os << "(" << tinyusdz::dtos(v.real) << ", " << tinyusdz::dtos(v.imag[0]) << ", " << tinyusdz::dtos(v.imag[1]) << ", " + << tinyusdz::dtos(v.imag[2]) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::quatd &v) { + os << "(" << tinyusdz::dtos(v.real) << ", " << tinyusdz::dtos(v.imag[0]) << ", " << tinyusdz::dtos(v.imag[1]) << ", " + << tinyusdz::dtos(v.imag[2]) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, + const tinyusdz::value::texcoord2h &v) { + os << "(" << v.s << ", " << v.t << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, + const tinyusdz::value::texcoord2f &v) { + os << "(" << tinyusdz::dtos(v.s) << ", " << tinyusdz::dtos(v.t) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, + const tinyusdz::value::texcoord2d &v) { + os << "(" << tinyusdz::dtos(v.s) << ", " << tinyusdz::dtos(v.t) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, + const tinyusdz::value::texcoord3h &v) { + os << "(" << v.s << ", " << v.t << ", " << v.r << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, + const tinyusdz::value::texcoord3f &v) { + os << "(" << tinyusdz::dtos(v.s) << ", " << tinyusdz::dtos(v.t) << ", " << tinyusdz::dtos(v.r) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &os, + const tinyusdz::value::texcoord3d &v) { + os << "(" << tinyusdz::dtos(v.s) << ", " << tinyusdz::dtos(v.t) << ", " << tinyusdz::dtos(v.r) << ")"; + return os; +} + +std::ostream &operator<<(std::ostream &ofs, + const tinyusdz::value::matrix2f &m) { + ofs << "( "; + + ofs << "(" << tinyusdz::dtos(m.m[0][0]) << ", " << tinyusdz::dtos(m.m[0][1]) << "), "; + ofs << "(" << tinyusdz::dtos(m.m[1][0]) << ", " << tinyusdz::dtos(m.m[1][1]) << ")"; + + ofs << " )"; + + return ofs; +} + +std::ostream &operator<<(std::ostream &ofs, + const tinyusdz::value::matrix3f &m) { + ofs << "( "; + + ofs << "(" << tinyusdz::dtos(m.m[0][0]) << ", " << tinyusdz::dtos(m.m[0][1]) << ", " << tinyusdz::dtos(m.m[0][2]) << "), "; + ofs << "(" << tinyusdz::dtos(m.m[1][0]) << ", " << tinyusdz::dtos(m.m[1][1]) << ", " << tinyusdz::dtos(m.m[1][2]) << "), "; + ofs << "(" << tinyusdz::dtos(m.m[2][0]) << ", " << tinyusdz::dtos(m.m[2][1]) << ", " << tinyusdz::dtos(m.m[2][2]) << ")"; + + ofs << " )"; + + return ofs; +} + +std::ostream &operator<<(std::ostream &ofs, + const tinyusdz::value::matrix4f &m) { + ofs << "( "; + + ofs << "(" << tinyusdz::dtos(m.m[0][0]) << ", " << tinyusdz::dtos(m.m[0][1]) << ", " << tinyusdz::dtos(m.m[0][2]) << ", " + << tinyusdz::dtos(m.m[0][3]) << "), "; + ofs << "(" << tinyusdz::dtos(m.m[1][0]) << ", " << tinyusdz::dtos(m.m[1][1]) << ", " << tinyusdz::dtos(m.m[1][2]) << ", " + << tinyusdz::dtos(m.m[1][3]) << "), "; + ofs << "(" << tinyusdz::dtos(m.m[2][0]) << ", " << tinyusdz::dtos(m.m[2][1]) << ", " << tinyusdz::dtos(m.m[2][2]) << ", " + << tinyusdz::dtos(m.m[2][3]) << "), "; + ofs << "(" << tinyusdz::dtos(m.m[3][0]) << ", " << tinyusdz::dtos(m.m[3][1]) << ", " << tinyusdz::dtos(m.m[3][2]) << ", " + << tinyusdz::dtos(m.m[3][3]) << ")"; + + ofs << " )"; + + return ofs; +} + +std::ostream &operator<<(std::ostream &ofs, + const tinyusdz::value::matrix2d &m) { + ofs << "( "; + + ofs << "(" << tinyusdz::dtos(m.m[0][0]) << ", " << tinyusdz::dtos(m.m[0][1]) << "), "; + ofs << "(" << tinyusdz::dtos(m.m[1][0]) << ", " << tinyusdz::dtos(m.m[1][1]) << ")"; + + ofs << " )"; + + return ofs; +} + +std::ostream &operator<<(std::ostream &ofs, + const tinyusdz::value::matrix3d &m) { + ofs << "( "; + + ofs << "(" << tinyusdz::dtos(m.m[0][0]) << ", " << tinyusdz::dtos(m.m[0][1]) << ", " << tinyusdz::dtos(m.m[0][2]) << "), "; + ofs << "(" << tinyusdz::dtos(m.m[1][0]) << ", " << tinyusdz::dtos(m.m[1][1]) << ", " << tinyusdz::dtos(m.m[1][2]) << "), "; + ofs << "(" << tinyusdz::dtos(m.m[2][0]) << ", " << tinyusdz::dtos(m.m[2][1]) << ", " << tinyusdz::dtos(m.m[2][2]) << ")"; + + ofs << " )"; + + return ofs; +} + +std::ostream &operator<<(std::ostream &ofs, + const tinyusdz::value::matrix4d &m) { + ofs << "( "; + + ofs << "(" << tinyusdz::dtos(m.m[0][0]) << ", " << tinyusdz::dtos(m.m[0][1]) << ", " << tinyusdz::dtos(m.m[0][2]) << ", " + << tinyusdz::dtos(m.m[0][3]) << "), "; + ofs << "(" << tinyusdz::dtos(m.m[1][0]) << ", " << tinyusdz::dtos(m.m[1][1]) << ", " << tinyusdz::dtos(m.m[1][2]) << ", " + << tinyusdz::dtos(m.m[1][3]) << "), "; + ofs << "(" << tinyusdz::dtos(m.m[2][0]) << ", " << tinyusdz::dtos(m.m[2][1]) << ", " << tinyusdz::dtos(m.m[2][2]) << ", " + << tinyusdz::dtos(m.m[2][3]) << "), "; + ofs << "(" << tinyusdz::dtos(m.m[3][0]) << ", " << tinyusdz::dtos(m.m[3][1]) << ", " << tinyusdz::dtos(m.m[3][2]) << ", " + << tinyusdz::dtos(m.m[3][3]) << ")"; + + ofs << " )"; + + return ofs; +} + +std::ostream &operator<<(std::ostream &ofs, const tinyusdz::value::token &tok) { + ofs << tinyusdz::quote(tok.str()); + + return ofs; +} + +#if 0 +std::ostream &operator<<(std::ostream &ofs, const tinyusdz::value::dict &m) { + ofs << "{\n"; + for (const auto &item : m) { + ofs << item.first << " = " << tinyusdz::value::pprint_any(item.second) + << "\n"; + } + ofs << "}"; + + return ofs; +} +#endif + +std::ostream &operator<<(std::ostream &ofs, + const tinyusdz::value::AssetPath &asset) { + std::string in_s = asset.GetAssetPath(); + + if (!in_s.empty()) { + std::string quote_str = "@"; + + std::string s; + + if (tinyusdz::contains(in_s, '@')) { + // Escape '@@@'(to '\@@@') if the input path contains '@@@' + for (size_t i = 0; i < in_s.length(); i++) { + if ((i + 2) < in_s.length()) { + if (in_s[i] == '@' && in_s[i + 1] == '@' && in_s[i + 2] == '@') { + s += "\\@@@"; + i += 2; + } else { + s += in_s[i]; + } + } + } + + quote_str = "@@@"; + } else { + s = in_s; + } + + // Do not escape backslash for asset path + ofs << quote_str << s << quote_str; + } + + return ofs; +} + +template<> +std::ostream &operator<<(std::ostream &ofs, const std::vector &v) { + + // Not sure what is the HARD-LIMT buffer length for dtoa_milo, + // but according to std::numeric_limits::digits10(=15), + // 32 should be sufficient, but allocate 128 just in case + char buf[128]; + + // TODO: multi-threading for further performance gain? + + ofs << "["; + for (size_t i = 0; i < v.size(); i++) { + if (i > 0) { + ofs << ", "; + } + dtoa_milo(v[i], buf); + ofs << std::string(buf); + } + ofs << "]"; + + return ofs; +} + +template<> +std::ostream &operator<<(std::ostream &ofs, const std::vector &v) { + + // Use floaxie + char buf[128]; + + // TODO: multi-threading for further performance gain? + + ofs << "["; + for (size_t i = 0; i < v.size(); i++) { + if (i > 0) { + ofs << ", "; + } + floaxie::ftoa(v[i], buf); + ofs << std::string(buf); + } + ofs << "]"; + + return ofs; +} + + +template<> +std::ostream &operator<<(std::ostream &ofs, const std::vector &v) { + +#if defined(TINYUSDZ_LOCAL_USE_JEAIII_ITOA) + // numeric_limits::digits10 is 19, so 32 should suffice. + char buf[32]; +#endif + + ofs << "["; + for (size_t i = 0; i < v.size(); i++) { + if (i > 0) { + ofs << ", "; + } +#if defined(TINYUSDZ_LOCAL_USE_JEAIII_ITOA) + tinyusdz::itoa(v[i], buf); + ofs << buf; +#else + ofs << v[i]; +#endif + } + ofs << "]"; + + return ofs; +} + +template<> +std::ostream &operator<<(std::ostream &ofs, const std::vector &v) { + +#if defined(TINYUSDZ_LOCAL_USE_JEAIII_ITOA) + char buf[32]; +#endif + + ofs << "["; + for (size_t i = 0; i < v.size(); i++) { + if (i > 0) { + ofs << ", "; + } +#if defined(TINYUSDZ_LOCAL_USE_JEAIII_ITOA) + tinyusdz::itoa(v[i], buf); + ofs << buf; +#else + ofs << v[i]; +#endif + } + ofs << "]"; + + return ofs; +} + +template<> +std::ostream &operator<<(std::ostream &ofs, const std::vector &v) { + +#if defined(TINYUSDZ_LOCAL_USE_JEAIII_ITOA) + // numeric_limits::digits10 is 19, so 32 should suffice. + char buf[32]; +#endif + + ofs << "["; + for (size_t i = 0; i < v.size(); i++) { + if (i > 0) { + ofs << ", "; + } +#if defined(TINYUSDZ_LOCAL_USE_JEAIII_ITOA) + tinyusdz::itoa(v[i], buf); + ofs << buf; +#else + ofs << v[i]; +#endif + } + ofs << "]"; + + return ofs; +} + +template<> +std::ostream &operator<<(std::ostream &ofs, const std::vector &v) { + +#if defined(TINYUSDZ_LOCAL_USE_JEAIII_ITOA) + char buf[32]; +#endif + + ofs << "["; + for (size_t i = 0; i < v.size(); i++) { + if (i > 0) { + ofs << ", "; + } +#if defined(TINYUSDZ_LOCAL_USE_JEAIII_ITOA) + tinyusdz::itoa(v[i], buf); + ofs << buf; +#else + ofs << v[i]; +#endif + } + ofs << "]"; + + return ofs; +} + + +} // namespace std + +namespace tinyusdz { +namespace value { + +// Simple brute-force way.. +// TODO: Use std::function or some template technique? +// NOTE: Use dedicated path for `float` and `double` + +#define CASE_EXPR_LIST(__FUNC) \ + __FUNC(bool) \ + __FUNC(half) \ + __FUNC(half2) \ + __FUNC(half3) \ + __FUNC(half4) \ + __FUNC(int32_t) \ + __FUNC(uint32_t) \ + __FUNC(int2) \ + __FUNC(int3) \ + __FUNC(int4) \ + __FUNC(uint2) \ + __FUNC(uint3) \ + __FUNC(uint4) \ + __FUNC(int64_t) \ + __FUNC(uint64_t) \ + __FUNC(float2) \ + __FUNC(float3) \ + __FUNC(float4) \ + __FUNC(double2) \ + __FUNC(double3) \ + __FUNC(double4) \ + __FUNC(matrix2d) \ + __FUNC(matrix3d) \ + __FUNC(matrix4d) \ + __FUNC(quath) \ + __FUNC(quatf) \ + __FUNC(quatd) \ + __FUNC(normal3h) \ + __FUNC(normal3f) \ + __FUNC(normal3d) \ + __FUNC(vector3h) \ + __FUNC(vector3f) \ + __FUNC(vector3d) \ + __FUNC(point3h) \ + __FUNC(point3f) \ + __FUNC(point3d) \ + __FUNC(color3f) \ + __FUNC(color3d) \ + __FUNC(color4f) \ + __FUNC(color4d) \ + __FUNC(texcoord2h) \ + __FUNC(texcoord2f) \ + __FUNC(texcoord2d) \ + __FUNC(texcoord3h) \ + __FUNC(texcoord3f) \ + __FUNC(texcoord3d) + +#define CASE_GPRIM_LIST(__FUNC) \ + __FUNC(Model) \ + __FUNC(Scope) \ + __FUNC(Xform) \ + __FUNC(GeomMesh) \ + __FUNC(GeomSphere) \ + __FUNC(GeomSubset) \ + __FUNC(GeomPoints) \ + __FUNC(GeomCube) \ + __FUNC(GeomCylinder) \ + __FUNC(GeomCapsule) \ + __FUNC(GeomCone) \ + __FUNC(GeomBasisCurves) \ + __FUNC(GeomNurbsCurves) \ + __FUNC(GeomCamera) \ + __FUNC(PointInstancer) \ + __FUNC(SphereLight) \ + __FUNC(DomeLight) \ + __FUNC(DiskLight) \ + __FUNC(DistantLight) \ + __FUNC(CylinderLight) \ + __FUNC(SkelRoot) \ + __FUNC(Skeleton) \ + __FUNC(SkelAnimation) \ + __FUNC(BlendShape) \ + __FUNC(Material) \ + __FUNC(Shader) + +#if 0 // remove +// std::ostream &operator<<(std::ostream &os, const any_value &v) { +// std::ostream &operator<<(std::ostream &os, const linb::any &v) { +std::string pprint_any(const linb::any &v, const uint32_t indent, + bool closing_brace) { +#define BASETYPE_CASE_EXPR(__ty) \ + case TypeTraits<__ty>::type_id(): { \ + os << linb::any_cast(v); \ + break; \ + } + +#define PRIMTYPE_CASE_EXPR(__ty) \ + case TypeTraits<__ty>::type_id(): { \ + os << to_string(linb::any_cast(v), indent, closing_brace); \ + break; \ + } + +#define ARRAY1DTYPE_CASE_EXPR(__ty) \ + case TypeTraits>::type_id(): { \ + os << linb::any_cast>(v); \ + break; \ + } + +#define ARRAY2DTYPE_CASE_EXPR(__ty) \ + case TypeTraits>>::type_id(): { \ + os << linb::any_cast>>(v); \ + break; \ + } + + std::stringstream os; + + switch (v.type_id()) { + // no `bool` type for 1D and 2D array + BASETYPE_CASE_EXPR(bool) + + // no std::vector and std::vector>, ... + BASETYPE_CASE_EXPR(dict) + + // base type + CASE_EXPR_LIST(BASETYPE_CASE_EXPR) + + // 1D array + CASE_EXPR_LIST(ARRAY1DTYPE_CASE_EXPR) + + // 2D array + CASE_EXPR_LIST(ARRAY2DTYPE_CASE_EXPR) + // Assume no 2D array of string-like data. + + // GPrim + CASE_GPRIM_LIST(PRIMTYPE_CASE_EXPR) + + // token, str: wrap with '"' + case TypeTraits::type_id(): { + os << quote(linb::any_cast(v).str()); + break; + } + case TypeTraits>::type_id(): { + const std::vector &lst = + linb::any_cast>(v); + std::vector vs; + std::transform(lst.begin(), lst.end(), std::back_inserter(vs), + [](const value::token &tok) { return tok.str(); }); + + os << quote(vs); + break; + } + case TypeTraits::type_id(): { + os << quote(linb::any_cast(v)); + break; + } + case TypeTraits>::type_id(): { + const std::vector &vs = + linb::any_cast>(v); + os << quote(vs); + break; + } + case TypeTraits::type_id(): { + os << "None"; + break; + } + + // TODO: List-up all case and remove `default` clause. + default: { + os << "ANY_PPRINT: TODO: (type: " << v.type_name() << ") "; + } + } + +#undef BASETYPE_CASE_EXPR +#undef PRIMTYPE_CASE_EXPR +#undef ARRAY1DTYPE_CASE_EXPR +#undef ARRAY2DTYPE_CASE_EXPR + + return os.str(); +} +#endif + +std::string pprint_value(const value::Value &v, const uint32_t indent, + bool closing_brace) { +#define BASETYPE_CASE_EXPR(__ty) \ + case TypeTraits<__ty>::type_id(): { \ + auto p = v.as<__ty>(); \ + if (p) { \ + os << (*p); \ + } else { \ + os << "[InternalError: Base type TypeId mismatch.]"; \ + } \ + break; \ + } + +#define PRIMTYPE_CASE_EXPR(__ty) \ + case TypeTraits<__ty>::type_id(): { \ + auto p = v.as<__ty>(); \ + if (p) { \ + os << to_string(*p, indent, closing_brace); \ + } else { \ + os << "[InternalError: Prim type TypeId mismatch.]"; \ + } \ + break; \ + } + +#define ARRAY1DTYPE_CASE_EXPR(__ty) \ + case TypeTraits>::type_id(): { \ + auto p = v.as>(); \ + if (p) { \ + os << (*p); \ + } else { \ + os << "[InternalError: 1D type TypeId mismatch.]"; \ + } \ + break; \ + } + + + + std::stringstream os; + + switch (v.type_id()) { + + // base type + CASE_EXPR_LIST(BASETYPE_CASE_EXPR) + + case TypeTraits::type_id(): { + auto p = v.as(); + if (p) { + os << dtos(*p); + } else { + os << "[InternalError: TypeId mismatch(`float` expected).]"; + } + break; + } + + case TypeTraits::type_id(): { + auto p = v.as(); + if (p) { + os << dtos(*p); + } else { + os << "[InternalError: TypeId mismatch(`double` expected).]"; + } + break; + } + + // 1D array + CASE_EXPR_LIST(ARRAY1DTYPE_CASE_EXPR) + + case TypeTraits>::type_id(): { + auto p = v.as>(); + if (p) { + os << (*p); + } else { + os << "[InternalError: TypeId mismatch(`float[]` expected).]"; + } + break; + } + + case TypeTraits>::type_id(): { + auto p = v.as>(); + if (p) { + os << (*p); + } else { + os << "[InternalError: TypeId mismatch(`double[]` expected).]"; + } + break; + } + + // 2D array + //CASE_EXPR_LIST(ARRAY2DTYPE_CASE_EXPR) + + // GPrim + CASE_GPRIM_LIST(PRIMTYPE_CASE_EXPR) + + // dict and customData + case TypeTraits::type_id(): { + auto p = v.as(); + if (p) { + os << print_customData(*p, "", indent); + } else { + os << "[InternalError: Dict type TypeId mismatch.]"; + } + break; + } + case TypeTraits::type_id(): { + auto p = v.as(); + if (p) { + os << (*p); + } else { + os << "[InternalError: asset type TypeId mismatch.]"; + } + break; + } + case TypeTraits>::type_id(): { + auto p = v.as>(); + if (p) { + os << (*p); + } else { + os << "[InternalError: asset[] type TypeId mismatch.]"; + } + break; + } + + case TypeTraits::type_id(): { + auto p = v.as(); + if (p) { + os << buildEscapedAndQuotedStringForUSDA(p->str()); + } else { + os << "[InternalError: Token type TypeId mismatch.]"; + } + break; + } + case TypeTraits>::type_id(): { + auto p = v.get_value>(); + if (p) { + std::vector vs; + std::transform(p->begin(), p->end(), std::back_inserter(vs), + [](const value::token &tok) { return buildEscapedAndQuotedStringForUSDA(tok.str()); }); + + os << vs; + } else { + os << "[InternalError: `token[]` type TypeId mismatch.]"; + } + break; + } + case TypeTraits::type_id(): { + auto p = v.as(); + if (p) { + os << buildEscapedAndQuotedStringForUSDA(*p); + } else { + os << "[InternalError: `string` type TypeId mismatch.]"; + } + break; + } + case TypeTraits::type_id(): { + auto p = v.as(); + if (p) { + os << (*p); // FIXME: Call buildEscapedAndQuotedStringForUSDA() here? + } else { + os << "[InternalError: `string` type TypeId mismatch.]"; + } + break; + } + case TypeTraits>::type_id(): { + auto p = v.as>(); + if (p) { + std::vector ss; + for (const auto &item : *p) { + ss.push_back(buildEscapedAndQuotedStringForUSDA(item)); + } + os << ss; // Use operator<<(std::vector) + } else { + os << "[InternalError: `string[]` type TypeId mismatch.]"; + } + break; + } + case TypeTraits>::type_id(): { + auto p = v.as>(); + if (p) { + os << (*p); + } else { + os << "[InternalError: `string[]` type TypeId mismatch.]"; + } + break; + } + case TypeTraits::type_id(): { + if (v.as()) { + os << "None"; + } else { + os << "[InternalError: ValueBlock type TypeId mismatch.]"; + } + break; + } + // TODO: List-up all case and remove `default` clause. + default: { + os << "VALUE_PPRINT: TODO: (type: " << v.type_name() << ") "; + } + } + +#undef BASETYPE_CASE_EXPR +#undef PRIMTYPE_CASE_EXPR +#undef ARRAY1DTYPE_CASE_EXPR +#undef ARRAY2DTYPE_CASE_EXPR + + return os.str(); +} + +#undef CASE_EXPR_LIST +#undef CASE_GPRIM_LIST + +} // namespace value +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/value-pprint.hh b/contrib/tinyusdz/tinyusdz_repo/src/value-pprint.hh new file mode 100644 index 000000000..e241b5f29 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/value-pprint.hh @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. + +#pragma once + +#include +#include +#include + +#include "value-types.hh" + +// forward decl +namespace tinyusdz { + +// in prim-types.hh +class Path; +struct Reference; +struct Payload; +struct LayerOffset; +struct SubLayer; +class Collection; + +} // namespace tinyusdz + +namespace std { + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::char2 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::char3 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::char4 &v); + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::uchar2 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::uchar3 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::uchar4 &v); + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::short2 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::short3 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::short4 &v); + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::ushort2 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::ushort3 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::ushort4 &v); + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::int2 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::int3 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::int4 &v); + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::uint2 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::uint3 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::uint4 &v); + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::half &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::half2 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::half3 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::half4 &v); + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::float2 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::float3 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::float4 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::double2 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::double3 &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::double4 &v); + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::point3h &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::point3f &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::point3d &v); + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::normal3h &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::normal3f &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::normal3d &v); + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::vector3h &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::vector3f &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::vector3d &v); + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::color3h &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::color3f &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::color3d &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::color4h &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::color4f &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::color4d &v); + +std::ostream &operator<<(std::ostream &os, + const tinyusdz::value::texcoord2h &v); +std::ostream &operator<<(std::ostream &os, + const tinyusdz::value::texcoord2f &v); +std::ostream &operator<<(std::ostream &os, + const tinyusdz::value::texcoord2d &v); + +std::ostream &operator<<(std::ostream &os, + const tinyusdz::value::texcoord3h &v); +std::ostream &operator<<(std::ostream &os, + const tinyusdz::value::texcoord3f &v); +std::ostream &operator<<(std::ostream &os, + const tinyusdz::value::texcoord3d &v); + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::quath &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::quatf &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::quatd &v); + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::token &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::dict &v); +std::ostream &operator<<(std::ostream &os, + const tinyusdz::value::TimeSamples &ts); + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::matrix2f &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::matrix3f &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::matrix4f &v); + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::matrix2d &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::matrix3d &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::matrix4d &v); + +std::ostream &operator<<(std::ostream &os, const tinyusdz::value::AssetPath &v); + +// NOTE: Implemented in pprinter.cc +std::ostream &operator<<(std::ostream &os, + const tinyusdz::value::StringData &v); + +// NOTE: Implemented in pprinter.cc +std::ostream &operator<<(std::ostream &os, const tinyusdz::Path &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::Reference &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::Payload &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::LayerOffset &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::SubLayer &v); +std::ostream &operator<<(std::ostream &os, const tinyusdz::Collection &v); + +// 1D array +template +std::ostream &operator<<(std::ostream &os, const std::vector &v) { + os << "["; + for (size_t i = 0; i < v.size(); i++) { + os << v[i]; + if (i != (v.size() - 1)) { + os << ", "; + } + } + os << "]"; + return os; +} + +// Provide specialized version for int and float array. +template <> +std::ostream &operator<<(std::ostream &os, const std::vector &v); + +template <> +std::ostream &operator<<(std::ostream &os, const std::vector &v); + +template <> +std::ostream &operator<<(std::ostream &os, const std::vector &v); + +template <> +std::ostream &operator<<(std::ostream &os, const std::vector &v); + +template <> +std::ostream &operator<<(std::ostream &os, const std::vector &v); + +template <> +std::ostream &operator<<(std::ostream &os, const std::vector &v); + +} // namespace std + +namespace tinyusdz { + +std::string to_string(bool v); +std::string to_string(int32_t v); +std::string to_string(uint32_t v); +std::string to_string(int64_t v); +std::string to_string(uint64_t v); +std::string to_string(const value::char2 &v); +std::string to_string(const value::char3 &v); +std::string to_string(const value::char4 &v); +std::string to_string(const value::short2 &v); +std::string to_string(const value::short3 &v); +std::string to_string(const value::short4 &v); +std::string to_string(const value::int2 &v); +std::string to_string(const value::int3 &v); +std::string to_string(const value::int4 &v); +std::string to_string(const value::uint2 &v); +std::string to_string(const value::uint3 &v); +std::string to_string(const value::uint4 &v); +std::string to_string(const value::float2 &v); +std::string to_string(const value::float3 &v); +std::string to_string(const value::float4 &v); +std::string to_string(const value::double2 &v); +std::string to_string(const value::double3 &v); +std::string to_string(const value::double4 &v); +std::string to_string(const value::double4 &v); +std::string to_string(const value::texcoord2h &v); +std::string to_string(const value::texcoord2f &v); +std::string to_string(const value::texcoord2d &v); +std::string to_string(const value::texcoord3h &v); +std::string to_string(const value::texcoord3f &v); +std::string to_string(const value::texcoord3d &v); +std::string to_string(const value::StringData &s); +std::string to_string(const value::token &s); +std::string to_string(const std::string &s); +std::string to_string(const value::quath &v); +std::string to_string(const value::quatf &v); +std::string to_string(const value::quatd &v); +std::string to_string(const value::matrix2f &v); +std::string to_string(const value::matrix3f &v); +std::string to_string(const value::matrix4f &v); +std::string to_string(const value::matrix2d &v); +std::string to_string(const value::matrix3d &v); +std::string to_string(const value::matrix4d &v); +std::string to_string(const value::frame4d &v); +std::string to_string(const value::half); +std::string to_string(const value::half2); +std::string to_string(const value::half3); +std::string to_string(const value::half4); +std::string to_string(const value::normal3h); +std::string to_string(const value::normal3f); +std::string to_string(const value::normal3d); +std::string to_string(const value::vector3h); +std::string to_string(const value::vector3f); +std::string to_string(const value::vector3d); +std::string to_string(const value::point3h); +std::string to_string(const value::point3f); +std::string to_string(const value::point3d); +std::string to_string(const value::color3f); +std::string to_string(const value::color3d); +std::string to_string(const value::color4h); +std::string to_string(const value::color4f); +std::string to_string(const value::color4d); + +namespace value { + +std::string pprint_value(const tinyusdz::value::Value &v, + const uint32_t indent = 0, bool closing_brace = true); + +// Print first N and last N items. +// 0 = print all items. +// Callee must ensure access to `vals` does not trigger out-of-bounds error. +template +std::string print_array_snipped(const T *vals, size_t n, size_t N = 16) { + std::stringstream os; + + if ((N == 0) || ((N * 2) >= n)) { + os << "["; + for (size_t i = 0; i < n; i++) { + if (i > 0) { + os << ", "; + } + os << vals[i]; + } + os << "]"; + } else { + size_t head_end = (std::min)(N, n); + size_t tail_start = (std::max)(n - N, head_end); + + os << "["; + + for (size_t i = 0; i < head_end; i++) { + if (i > 0) { + os << ", "; + } + os << vals[i]; + } + + os << ", ..., "; + + for (size_t i = tail_start; i < n; i++) { + if (i > tail_start) { + os << ", "; + } + os << vals[i]; + } + + os << "]"; + } + return os.str(); +} + +// Account for stride. +// stride 0 => use sizeof(T) +// Callee must ensure access to `vals` does not trigger out-of-bounds error. +template +std::string print_strided_array_snipped(const uint8_t *vals, size_t stride_bytes, const size_t n, size_t N = 16) { + std::stringstream os; + + if ((stride_bytes == 0) || (stride_bytes == sizeof(T))) { // tightly packed. + return print_array_snipped(reinterpret_cast(vals), n, N); + } + + if ((N == 0) || ((N * 2) >= n)) { + os << "["; + for (size_t i = 0; i < n; i++) { + if (i > 0) { + os << ", "; + } + os << *reinterpret_cast(&vals[i * stride_bytes]); + } + os << "]"; + } else { + size_t head_end = (std::min)(N, n); + size_t tail_start = (std::max)(n - N, head_end); + + os << "["; + + for (size_t i = 0; i < head_end; i++) { + if (i > 0) { + os << ", "; + } + os << *reinterpret_cast(&vals[i * stride_bytes]); + } + + os << ", ..., "; + + for (size_t i = tail_start; i < n; i++) { + if (i > tail_start) { + os << ", "; + } + os << *reinterpret_cast(&vals[i * stride_bytes]); + } + + os << "]"; + } + return os.str(); +} + +// Print first N and last N items. +// 0 = print all items. +// Useful when dump +template +std::string print_array_snipped(const std::vector &vals, size_t N = 16) { + std::stringstream os; + + if ((N == 0) || ((N * 2) >= vals.size())) { + os << "["; + for (size_t i = 0; i < vals.size(); i++) { + if (i > 0) { + os << ", "; + } + os << vals[i]; + } + os << "]"; + } else { + size_t head_end = (std::min)(N, vals.size()); + size_t tail_start = (std::max)(vals.size() - N, head_end); + + os << "["; + + for (size_t i = 0; i < head_end; i++) { + if (i > 0) { + os << ", "; + } + os << vals[i]; + } + + os << ", ..., "; + + for (size_t i = tail_start; i < vals.size(); i++) { + if (i > tail_start) { + os << ", "; + } + os << vals[i]; + } + + os << "]"; + } + return os.str(); +} + +// TODO: Remove +// std::string pprint_any(const linb::any &v, const uint32_t indent = 0, bool +// closing_brace = true); + +} // namespace value +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/value-type-macros.inc b/contrib/tinyusdz/tinyusdz_repo/src/value-type-macros.inc new file mode 100644 index 000000000..10b196520 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/value-type-macros.inc @@ -0,0 +1,217 @@ +// Apply __FUNC to value types(but no string types). +#define APPLY_FUNC_TO_VALUE_TYPES_NO_STRING(__FUNC) \ + __FUNC(bool) \ + __FUNC(value::AssetPath) \ + __FUNC(value::token) \ + __FUNC(value::half) \ + __FUNC(value::half2) \ + __FUNC(value::half3) \ + __FUNC(value::half4) \ + __FUNC(int32_t) \ + __FUNC(uint32_t) \ + __FUNC(value::int2) \ + __FUNC(value::int3) \ + __FUNC(value::int4) \ + __FUNC(value::uint2) \ + __FUNC(value::uint3) \ + __FUNC(value::uint4) \ + __FUNC(int64_t) \ + __FUNC(uint64_t) \ + __FUNC(float) \ + __FUNC(value::float2) \ + __FUNC(value::float3) \ + __FUNC(value::float4) \ + __FUNC(double) \ + __FUNC(value::double2) \ + __FUNC(value::double3) \ + __FUNC(value::double4) \ + __FUNC(value::quath) \ + __FUNC(value::quatf) \ + __FUNC(value::quatd) \ + __FUNC(value::normal3h) \ + __FUNC(value::normal3f) \ + __FUNC(value::normal3d) \ + __FUNC(value::vector3h) \ + __FUNC(value::vector3f) \ + __FUNC(value::vector3d) \ + __FUNC(value::point3h) \ + __FUNC(value::point3f) \ + __FUNC(value::point3d) \ + __FUNC(value::color3f) \ + __FUNC(value::color3d) \ + __FUNC(value::color4h) \ + __FUNC(value::color4f) \ + __FUNC(value::color4d) \ + __FUNC(value::texcoord2h) \ + __FUNC(value::texcoord2f) \ + __FUNC(value::texcoord2d) \ + __FUNC(value::texcoord3h) \ + __FUNC(value::texcoord3f) \ + __FUNC(value::texcoord3d) \ + __FUNC(value::matrix2d) \ + __FUNC(value::matrix3d) \ + __FUNC(value::matrix4d) \ + __FUNC(value::frame4d) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + +// Apply __FUNC to value types. +#define APPLY_FUNC_TO_VALUE_TYPES(__FUNC) \ + APPLY_FUNC_TO_VALUE_TYPES_NO_STRING(__FUNC) \ + __FUNC(std::string) \ + __FUNC(value::StringData) \ + +// Apply __FUNC to numeric value types. +#define APPLY_FUNC_TO_NUMERIC_VALUE_TYPES(__FUNC) \ + __FUNC(bool) \ + __FUNC(value::half) \ + __FUNC(value::half2) \ + __FUNC(value::half3) \ + __FUNC(value::half4) \ + __FUNC(int32_t) \ + __FUNC(uint32_t) \ + __FUNC(value::int2) \ + __FUNC(value::int3) \ + __FUNC(value::int4) \ + __FUNC(value::uint2) \ + __FUNC(value::uint3) \ + __FUNC(value::uint4) \ + __FUNC(int64_t) \ + __FUNC(uint64_t) \ + __FUNC(float) \ + __FUNC(value::float2) \ + __FUNC(value::float3) \ + __FUNC(value::float4) \ + __FUNC(double) \ + __FUNC(value::double2) \ + __FUNC(value::double3) \ + __FUNC(value::double4) \ + __FUNC(value::quath) \ + __FUNC(value::quatf) \ + __FUNC(value::quatd) \ + __FUNC(value::normal3h) \ + __FUNC(value::normal3f) \ + __FUNC(value::normal3d) \ + __FUNC(value::vector3h) \ + __FUNC(value::vector3f) \ + __FUNC(value::vector3d) \ + __FUNC(value::point3h) \ + __FUNC(value::point3f) \ + __FUNC(value::point3d) \ + __FUNC(value::color3f) \ + __FUNC(value::color3d) \ + __FUNC(value::color4h) \ + __FUNC(value::color4f) \ + __FUNC(value::color4d) \ + __FUNC(value::texcoord2h) \ + __FUNC(value::texcoord2f) \ + __FUNC(value::texcoord2d) \ + __FUNC(value::texcoord3h) \ + __FUNC(value::texcoord3f) \ + __FUNC(value::texcoord3d) \ + __FUNC(value::matrix2d) \ + __FUNC(value::matrix3d) \ + __FUNC(value::matrix4d) \ + __FUNC(value::frame4d) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) \ + __FUNC(std::vector) diff --git a/contrib/tinyusdz/tinyusdz_repo/src/value-types.cc b/contrib/tinyusdz/tinyusdz_repo/src/value-types.cc new file mode 100644 index 000000000..b870284ef --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/value-types.cc @@ -0,0 +1,1141 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. +#include "value-types.hh" + +#include "str-util.hh" +#include "value-pprint.hh" +#include "value-eval-util.hh" + +// +#include "common-macros.inc" + +// For compile-time map +// Another candidate is frozen: https://github.com/serge-sans-paille/frozen +// +#include "external/mapbox/eternal/include/mapbox/eternal.hpp" + +namespace tinyusdz { +namespace value { + +// +// Supported type for `Linear` interpolation +// +// half, float, double, TimeCode(double) +// matrix2d, matrix3d, matrix4d, +// float2h, float3h, float4h +// float2f, float3f, float4f +// float2d, float3d, float4d +// quath, quatf, quatd +// (use slerp for quaternion type) +bool IsLerpSupportedType(uint32_t tyid) { + + // See underlying_type_id to simplify check for Role types(e.g. color3f) +#define IS_SUPPORTED_TYPE(__tyid, __ty) \ + if (__tyid == value::TypeTraits<__ty>::underlying_type_id()) return true + + IS_SUPPORTED_TYPE(tyid, value::half); + IS_SUPPORTED_TYPE(tyid, value::half2); + IS_SUPPORTED_TYPE(tyid, value::half3); + IS_SUPPORTED_TYPE(tyid, value::half4); + IS_SUPPORTED_TYPE(tyid, float); + IS_SUPPORTED_TYPE(tyid, value::float2); + IS_SUPPORTED_TYPE(tyid, value::float3); + IS_SUPPORTED_TYPE(tyid, value::float4); + IS_SUPPORTED_TYPE(tyid, double); + IS_SUPPORTED_TYPE(tyid, value::double2); + IS_SUPPORTED_TYPE(tyid, value::double3); + IS_SUPPORTED_TYPE(tyid, value::double4); + IS_SUPPORTED_TYPE(tyid, value::quath); + IS_SUPPORTED_TYPE(tyid, value::quatf); + IS_SUPPORTED_TYPE(tyid, value::quatd); + IS_SUPPORTED_TYPE(tyid, value::matrix2d); + IS_SUPPORTED_TYPE(tyid, value::matrix3d); + IS_SUPPORTED_TYPE(tyid, value::matrix4d); + +#undef IS_SUPPORTED_TYPE + + return false; +} + +bool Lerp(const value::Value &a, const value::Value &b, double dt, value::Value *dst) { + if (!dst) { + return false; + } + + if (a.type_id() != b.type_id()) { + return false; + } + + uint32_t tyid = a.type_id(); + + if (!IsLerpSupportedType(tyid)) { + return false; + } + + bool ok{false}; + value::Value result; + +#define DO_LERP(__ty) \ + if (tyid == value::TypeTraits<__ty>::type_id()) { \ + const __ty *v0 = a.as<__ty>(); \ + const __ty *v1 = b.as<__ty>(); \ + __ty c; \ + if (v0 && v1) { \ + c = lerp(*v0, *v1, dt); \ + result = c; \ + ok = true; \ + } \ + } else + + DO_LERP(value::half) + DO_LERP(value::half2) + DO_LERP(value::half3) + DO_LERP(value::half4) + DO_LERP(float) + DO_LERP(value::float2) + DO_LERP(value::float3) + DO_LERP(value::float4) + DO_LERP(double) + DO_LERP(value::double2) + DO_LERP(value::double3) + DO_LERP(value::double4) + DO_LERP(value::quath) + DO_LERP(value::quatf) + DO_LERP(value::quatd) + DO_LERP(value::color3h) + DO_LERP(value::color3f) + DO_LERP(value::color3d) + DO_LERP(value::color4h) + DO_LERP(value::color4f) + DO_LERP(value::color4d) + DO_LERP(value::point3h) + DO_LERP(value::point3f) + DO_LERP(value::point3d) + DO_LERP(value::normal3h) + DO_LERP(value::normal3f) + DO_LERP(value::normal3d) + DO_LERP(value::vector3h) + DO_LERP(value::vector3f) + DO_LERP(value::vector3d) + DO_LERP(value::texcoord2h) + DO_LERP(value::texcoord2f) + DO_LERP(value::texcoord2d) + DO_LERP(value::texcoord3h) + DO_LERP(value::texcoord3f) + DO_LERP(value::texcoord3d) + { + DCOUT("TODO: type " << GetTypeName(tyid)); + } + +#undef DO_LERP + + if (ok) { + (*dst) = result; + } + + return ok; +} + + +#if 0 // TODO: Remove +bool Reconstructor::reconstruct(AttribMap &amap) { + err_.clear(); + + staticstruct::Reader r; + +#define CONVERT_TYPE_SCALAR(__ty, __value) \ + case TypeTraits<__ty>::type_id: { \ + __ty *p = reinterpret_cast<__ty *>(__value); \ + staticstruct::Handler<__ty> _h(p); \ + return _h.write(&handler); \ + } + +#define CONVERT_TYPE_1D(__ty, __value) \ + case (TypeTraits<__ty>::type_id | TYPE_ID_1D_ARRAY_BIT): { \ + std::vector<__ty> *p = reinterpret_cast *>(__value); \ + staticstruct::Handler> _h(p); \ + return _h.write(&handler); \ + } + +#define CONVERT_TYPE_2D(__ty, __value) \ + case (TypeTraits<__ty>::type_id | TYPE_ID_2D_ARRAY_BIT): { \ + std::vector> *p = \ + reinterpret_cast> *>(__value); \ + staticstruct::Handler>> _h(p); \ + return _h.write(&handler); \ + } + +#define CONVERT_TYPE_LIST(__FUNC) \ + __FUNC(half, v) \ + __FUNC(half2, v) \ + __FUNC(half3, v) \ + __FUNC(half4, v) \ + __FUNC(int32_t, v) \ + __FUNC(uint32_t, v) \ + __FUNC(int2, v) \ + __FUNC(int3, v) \ + __FUNC(int4, v) \ + __FUNC(uint2, v) \ + __FUNC(uint3, v) \ + __FUNC(uint4, v) \ + __FUNC(int64_t, v) \ + __FUNC(uint64_t, v) \ + __FUNC(float, v) \ + __FUNC(float2, v) \ + __FUNC(float3, v) \ + __FUNC(float4, v) \ + __FUNC(double, v) \ + __FUNC(double2, v) \ + __FUNC(double3, v) \ + __FUNC(double4, v) \ + __FUNC(quath, v) \ + __FUNC(quatf, v) \ + __FUNC(quatd, v) \ + __FUNC(vector3h, v) \ + __FUNC(vector3f, v) \ + __FUNC(vector3d, v) \ + __FUNC(normal3h, v) \ + __FUNC(normal3f, v) \ + __FUNC(normal3d, v) \ + __FUNC(point3h, v) \ + __FUNC(point3f, v) \ + __FUNC(point3d, v) \ + __FUNC(color3f, v) \ + __FUNC(color3d, v) \ + __FUNC(color4f, v) \ + __FUNC(color4d, v) \ + __FUNC(matrix2d, v) \ + __FUNC(matrix3d, v) \ + __FUNC(matrix4d, v) + + bool ret = r.ParseStruct( + &h, + [&amap](std::string key, uint32_t flags, uint32_t user_type_id, + staticstruct::BaseHandler &handler) -> bool { + std::cout << "key = " << key << ", count = " << amap.attribs.count(key) + << "\n"; + + if (!amap.attribs.count(key)) { + if (flags & staticstruct::Flags::Optional) { + return true; + } else { + return false; + } + } + + auto &value = amap.attribs[key]; + if (amap.attribs[key].type_id() == user_type_id) { + void *v = value.value(); + + switch (user_type_id) { + CONVERT_TYPE_SCALAR(bool, v) + + CONVERT_TYPE_LIST(CONVERT_TYPE_SCALAR) + CONVERT_TYPE_LIST(CONVERT_TYPE_1D) + CONVERT_TYPE_LIST(CONVERT_TYPE_2D) + + default: { + std::cerr << "Unsupported type: " << GetTypeName(user_type_id) + << "\n"; + return false; + } + } + } else { + std::cerr << "type: " << amap.attribs[key].type_name() << "(a.k.a " + << amap.attribs[key].underlying_type_name() + << ") expected but got " << GetTypeName(user_type_id) + << " for attribute \"" << key << "\"\n"; + return false; + } + }, + &err_); + + return ret; + +#undef CONVERT_TYPE_SCALAR +#undef CONVERT_TYPE_1D +#undef CONVERT_TYPE_2D +#undef CONVERT_TYPE_LIST +} +#endif + +nonstd::optional TryGetTypeName(uint32_t tyid) { + MAPBOX_ETERNAL_CONSTEXPR const auto tynamemap = + mapbox::eternal::map({ + {TYPE_ID_TOKEN, kToken}, + {TYPE_ID_STRING, kString}, + {TYPE_ID_STRING, kPath}, + {TYPE_ID_ASSET_PATH, kAssetPath}, + {TYPE_ID_DICT, kDictionary}, + {TYPE_ID_TIMECODE, kTimeCode}, + {TYPE_ID_BOOL, kBool}, + {TYPE_ID_UCHAR, kUChar}, + {TYPE_ID_HALF, kHalf}, + {TYPE_ID_INT32, kInt}, + {TYPE_ID_UINT32, kUInt}, + {TYPE_ID_INT64, kInt64}, + {TYPE_ID_UINT64, kUInt64}, + {TYPE_ID_INT2, kInt2}, + {TYPE_ID_INT3, kInt3}, + {TYPE_ID_INT4, kInt4}, + {TYPE_ID_UINT2, kUInt2}, + {TYPE_ID_UINT3, kUInt3}, + {TYPE_ID_UINT4, kUInt4}, + {TYPE_ID_HALF2, kHalf2}, + {TYPE_ID_HALF3, kHalf3}, + {TYPE_ID_HALF4, kHalf4}, + {TYPE_ID_MATRIX2D, kMatrix2d}, + {TYPE_ID_MATRIX3D, kMatrix3d}, + {TYPE_ID_MATRIX4D, kMatrix4d}, + {TYPE_ID_FLOAT, kFloat}, + {TYPE_ID_FLOAT2, kFloat2}, + {TYPE_ID_FLOAT3, kFloat3}, + {TYPE_ID_FLOAT4, kFloat4}, + {TYPE_ID_DOUBLE, kDouble}, + {TYPE_ID_DOUBLE2, kDouble2}, + {TYPE_ID_DOUBLE3, kDouble3}, + {TYPE_ID_DOUBLE4, kDouble4}, + {TYPE_ID_QUATH, kQuath}, + {TYPE_ID_QUATF, kQuatf}, + {TYPE_ID_QUATD, kQuatd}, + {TYPE_ID_VECTOR3H, kVector3h}, + {TYPE_ID_VECTOR3F, kVector3f}, + {TYPE_ID_VECTOR3D, kVector3d}, + {TYPE_ID_POINT3H, kPoint3h}, + {TYPE_ID_POINT3F, kPoint3f}, + {TYPE_ID_POINT3D, kPoint3d}, + {TYPE_ID_NORMAL3H, kNormal3h}, + {TYPE_ID_NORMAL3F, kNormal3f}, + {TYPE_ID_NORMAL3D, kNormal3d}, + {TYPE_ID_COLOR3F, kColor3f}, + {TYPE_ID_COLOR3D, kColor3d}, + {TYPE_ID_COLOR4F, kColor4f}, + {TYPE_ID_COLOR4D, kColor4d}, + {TYPE_ID_FRAME4D, kFrame4d}, + {TYPE_ID_TEXCOORD2H, kTexCoord2h}, + {TYPE_ID_TEXCOORD2F, kTexCoord2f}, + {TYPE_ID_TEXCOORD2D, kTexCoord2d}, + {TYPE_ID_TEXCOORD3H, kTexCoord3h}, + {TYPE_ID_TEXCOORD3F, kTexCoord3f}, + {TYPE_ID_TEXCOORD3D, kTexCoord3d}, + {TYPE_ID_RELATIONSHIP, kRelationship}, + }); + + bool array_bit = (TYPE_ID_1D_ARRAY_BIT & tyid); + uint32_t scalar_tid = tyid & (~TYPE_ID_1D_ARRAY_BIT); + + auto ret = tynamemap.find(scalar_tid); + if (ret != tynamemap.end()) { + std::string s = ret->second.c_str(); + if (array_bit) { + s += "[]"; + } + return std::move(s); + } + + return nonstd::nullopt; +} + +std::string GetTypeName(uint32_t tyid) { + auto ret = TryGetTypeName(tyid); + + if (!ret) { + return "(GetTypeName) [[Unknown or unimplemented/unsupported type_id: " + + std::to_string(tyid) + "]]"; + } + + return ret.value(); +} + +nonstd::optional TryGetTypeId(const std::string &tyname) { + MAPBOX_ETERNAL_CONSTEXPR const auto tyidmap = + mapbox::eternal::hash_map({ + {kToken, TYPE_ID_TOKEN}, + {kString, TYPE_ID_STRING}, + {kPath, TYPE_ID_STRING}, + {kAssetPath, TYPE_ID_ASSET_PATH}, + {kDictionary, TYPE_ID_DICT}, + {kTimeCode, TYPE_ID_TIMECODE}, + {kBool, TYPE_ID_BOOL}, + {kUChar, TYPE_ID_UCHAR}, + {kHalf, TYPE_ID_HALF}, + {kInt, TYPE_ID_INT32}, + {kUInt, TYPE_ID_UINT32}, + {kInt64, TYPE_ID_INT64}, + {kUInt64, TYPE_ID_UINT64}, + {kInt2, TYPE_ID_INT2}, + {kInt3, TYPE_ID_INT3}, + {kInt4, TYPE_ID_INT4}, + {kUInt2, TYPE_ID_UINT2}, + {kUInt3, TYPE_ID_UINT3}, + {kUInt4, TYPE_ID_UINT4}, + {kHalf2, TYPE_ID_HALF2}, + {kHalf3, TYPE_ID_HALF3}, + {kHalf4, TYPE_ID_HALF4}, + {kMatrix2d, TYPE_ID_MATRIX2D}, + {kMatrix3d, TYPE_ID_MATRIX3D}, + {kMatrix4d, TYPE_ID_MATRIX4D}, + {kFloat, TYPE_ID_FLOAT}, + {kFloat2, TYPE_ID_FLOAT2}, + {kFloat3, TYPE_ID_FLOAT3}, + {kFloat4, TYPE_ID_FLOAT4}, + {kDouble, TYPE_ID_DOUBLE}, + {kDouble2, TYPE_ID_DOUBLE2}, + {kDouble3, TYPE_ID_DOUBLE3}, + {kDouble4, TYPE_ID_DOUBLE4}, + {kQuath, TYPE_ID_QUATH}, + {kQuatf, TYPE_ID_QUATF}, + {kQuatd, TYPE_ID_QUATD}, + {kVector3h, TYPE_ID_VECTOR3H}, + {kVector3f, TYPE_ID_VECTOR3F}, + {kVector3d, TYPE_ID_VECTOR3D}, + {kPoint3h, TYPE_ID_POINT3H}, + {kPoint3f, TYPE_ID_POINT3F}, + {kPoint3d, TYPE_ID_POINT3D}, + {kNormal3h, TYPE_ID_NORMAL3H}, + {kNormal3f, TYPE_ID_NORMAL3F}, + {kNormal3d, TYPE_ID_NORMAL3D}, + {kColor3f, TYPE_ID_COLOR3F}, + {kColor3d, TYPE_ID_COLOR3D}, + {kColor4f, TYPE_ID_COLOR4F}, + {kColor4d, TYPE_ID_COLOR4D}, + {kFrame4d, TYPE_ID_FRAME4D}, + {kTexCoord2h, TYPE_ID_TEXCOORD2H}, + {kTexCoord2f, TYPE_ID_TEXCOORD2F}, + {kTexCoord2d, TYPE_ID_TEXCOORD2D}, + {kTexCoord3h, TYPE_ID_TEXCOORD3H}, + {kTexCoord3f, TYPE_ID_TEXCOORD3F}, + {kTexCoord3d, TYPE_ID_TEXCOORD3D}, + {kRelationship, TYPE_ID_RELATIONSHIP}, + }); + + std::string s = tyname; + uint32_t array_bit = 0; + if (endsWith(tyname, "[]")) { + s = removeSuffix(s, "[]"); + array_bit |= TYPE_ID_1D_ARRAY_BIT; + } + + // It looks USD does not support 2D array type, so no further `[]` check + + auto ret = tyidmap.find(s.c_str()); + if (ret != tyidmap.end()) { + return ret->second | array_bit; + } + + return nonstd::nullopt; +} + +uint32_t GetTypeId(const std::string &tyname) { + auto ret = TryGetTypeId(tyname); + + if (!ret) { + return TYPE_ID_INVALID; + } + + return ret.value(); +} + +nonstd::optional TryGetUnderlyingTypeId(const std::string &tyname) { + MAPBOX_ETERNAL_CONSTEXPR const auto utyidmap = + mapbox::eternal::hash_map({ + {kPoint3h, TYPE_ID_HALF3}, + {kPoint3f, TYPE_ID_FLOAT3}, + {kPoint3d, TYPE_ID_DOUBLE3}, + {kNormal3h, TYPE_ID_HALF3}, + {kNormal3f, TYPE_ID_FLOAT3}, + {kNormal3d, TYPE_ID_DOUBLE3}, + {kVector3h, TYPE_ID_HALF3}, + {kVector3f, TYPE_ID_FLOAT3}, + {kVector3d, TYPE_ID_DOUBLE3}, + {kColor3h, TYPE_ID_HALF3}, + {kColor3f, TYPE_ID_FLOAT3}, + {kColor3d, TYPE_ID_DOUBLE3}, + {kColor4h, TYPE_ID_HALF4}, + {kColor4f, TYPE_ID_FLOAT4}, + {kColor4d, TYPE_ID_DOUBLE4}, + {kTexCoord2h, TYPE_ID_HALF2}, + {kTexCoord2f, TYPE_ID_FLOAT2}, + {kTexCoord2d, TYPE_ID_DOUBLE3}, + {kTexCoord3h, TYPE_ID_HALF3}, + {kTexCoord3f, TYPE_ID_FLOAT3}, + {kTexCoord3d, TYPE_ID_DOUBLE4}, + {kFrame4d, TYPE_ID_MATRIX4D}, + }); + + { + std::string s = tyname; + uint32_t array_bit = 0; + if (endsWith(tyname, "[]")) { + s = removeSuffix(s, "[]"); + array_bit |= TYPE_ID_1D_ARRAY_BIT; + } + + auto ret = utyidmap.find(s.c_str()); + if (ret != utyidmap.end()) { + return ret->second | array_bit; + } + } + + // Fallback + return TryGetTypeId(tyname); +} + +uint32_t GetUnderlyingTypeId(const std::string &tyname) { + auto ret = TryGetUnderlyingTypeId(tyname); + + if (!ret) { + return TYPE_ID_INVALID; + } + + return ret.value(); +} + +nonstd::optional TryGetUnderlyingTypeName(const uint32_t tyid) { + MAPBOX_ETERNAL_CONSTEXPR const auto utynamemap = + mapbox::eternal::map({ + {TYPE_ID_POINT3H, kHalf3}, + {TYPE_ID_POINT3F, kFloat3}, + {TYPE_ID_POINT3D, kDouble3}, + {TYPE_ID_NORMAL3H, kHalf3}, + {TYPE_ID_NORMAL3F, kFloat3}, + {TYPE_ID_NORMAL3D, kDouble3}, + {TYPE_ID_VECTOR3H, kHalf3}, + {TYPE_ID_VECTOR3F, kFloat3}, + {TYPE_ID_VECTOR3D, kDouble3}, + {TYPE_ID_COLOR3H, kHalf3}, + {TYPE_ID_COLOR3F, kFloat3}, + {TYPE_ID_COLOR3D, kDouble3}, + {TYPE_ID_COLOR4H, kHalf4}, + {TYPE_ID_COLOR4F, kFloat4}, + {TYPE_ID_COLOR4D, kDouble4}, + {TYPE_ID_TEXCOORD2H, kHalf2}, + {TYPE_ID_TEXCOORD2F, kFloat2}, + {TYPE_ID_TEXCOORD2D, kDouble2}, + {TYPE_ID_TEXCOORD3H, kHalf3}, + {TYPE_ID_TEXCOORD3F, kFloat3}, + {TYPE_ID_TEXCOORD3D, kDouble3}, + {TYPE_ID_FRAME4D, kMatrix4d}, + }); + + { + bool array_bit = (TYPE_ID_1D_ARRAY_BIT & tyid); + uint32_t scalar_tid = tyid & (~TYPE_ID_1D_ARRAY_BIT); + + auto ret = utynamemap.find(scalar_tid); + if (ret != utynamemap.end()) { + std::string s = ret->second.c_str(); + if (array_bit) { + s += "[]"; + } + return std::move(s); + } + } + + return TryGetTypeName(tyid); + +} + +std::string GetUnderlyingTypeName(uint32_t tyid) { + auto ret = TryGetUnderlyingTypeName(tyid); + + if (!ret) { + return "(GetUnderlyingTypeName) [[Unknown or unimplemented/unsupported type_id: " + + std::to_string(tyid) + "]]"; + } + + return ret.value(); +} + +bool IsRoleType(const std::string &tyname) { + return GetUnderlyingTypeId(tyname) != value::TYPE_ID_INVALID; +} + +bool IsRoleType(const uint32_t tyid) { + return GetUnderlyingTypeId(GetTypeName(tyid)) != value::TYPE_ID_INVALID; +} + +// +// half float +// +namespace { + +// https://www.realtime.bc.ca/articles/endian-safe.html +union HostEndianness { + int i; + char c[sizeof(int)]; + + HostEndianness() : i(1) {} + + bool isBig() const { return c[0] == 0; } + bool isLittle() const { return c[0] != 0; } +}; + +// https://gist.github.com/rygorous/2156668 +// Little endian +union FP32le { + unsigned int u; + float f; + struct { + unsigned int Mantissa : 23; + unsigned int Exponent : 8; + unsigned int Sign : 1; + } s; +}; + +// Big endian +union FP32be { + unsigned int u; + float f; + struct { + unsigned int Sign : 1; + unsigned int Exponent : 8; + unsigned int Mantissa : 23; + } s; +}; + +// Little endian +union float16le { + unsigned short u; + struct { + unsigned int Mantissa : 10; + unsigned int Exponent : 5; + unsigned int Sign : 1; + } s; +}; + +// Big endian +union float16be { + unsigned short u; + struct { + unsigned int Sign : 1; + unsigned int Exponent : 5; + unsigned int Mantissa : 10; + } s; +}; + +float half_to_float_le(float16le h) { + static const FP32le magic = {113 << 23}; + static const unsigned int shifted_exp = 0x7c00 + << 13; // exponent mask after shift + FP32le o; + + o.u = (h.u & 0x7fffU) << 13U; // exponent/mantissa bits + unsigned int exp_ = shifted_exp & o.u; // just the exponent + o.u += (127 - 15) << 23; // exponent adjust + + // handle exponent special cases + if (exp_ == shifted_exp) // Inf/NaN? + o.u += (128 - 16) << 23; // extra exp adjust + else if (exp_ == 0) // Zero/Denormal? + { + o.u += 1 << 23; // extra exp adjust + o.f -= magic.f; // renormalize + } + + o.u |= (h.u & 0x8000U) << 16U; // sign bit + return o.f; +} + +float half_to_float_be(float16be h) { + static const FP32be magic = {113 << 23}; + static const unsigned int shifted_exp = 0x7c00 + << 13; // exponent mask after shift + FP32be o; + + o.u = (h.u & 0x7fffU) << 13U; // exponent/mantissa bits + unsigned int exp_ = shifted_exp & o.u; // just the exponent + o.u += (127 - 15) << 23; // exponent adjust + + // handle exponent special cases + if (exp_ == shifted_exp) // Inf/NaN? + o.u += (128 - 16) << 23; // extra exp adjust + else if (exp_ == 0) // Zero/Denormal? + { + o.u += 1 << 23; // extra exp adjust + o.f -= magic.f; // renormalize + } + + o.u |= (h.u & 0x8000U) << 16U; // sign bit + return o.f; +} + +half float_to_half_full_be(float _f) { + FP32be f; + f.f = _f; + float16be o = {0}; + + // Based on ISPC reference code (with minor modifications) + if (f.s.Exponent == 0) // Signed zero/denormal (which will underflow) + o.s.Exponent = 0; + else if (f.s.Exponent == 255) // Inf or NaN (all exponent bits set) + { + o.s.Exponent = 31; + o.s.Mantissa = f.s.Mantissa ? 0x200 : 0; // NaN->qNaN and Inf->Inf + } else // Normalized number + { + // Exponent unbias the single, then bias the halfp + int newexp = f.s.Exponent - 127 + 15; + if (newexp >= 31) // Overflow, return signed infinity + o.s.Exponent = 31; + else if (newexp <= 0) // Underflow + { + if ((14 - newexp) <= 24) // Mantissa might be non-zero + { + unsigned int mant = f.s.Mantissa | 0x800000; // Hidden 1 bit + o.s.Mantissa = mant >> (14 - newexp); + if ((mant >> (13 - newexp)) & 1) // Check for rounding + o.u++; // Round, might overflow into exp bit, but this is OK + } + } else { + o.s.Exponent = static_cast(newexp); + o.s.Mantissa = f.s.Mantissa >> 13; + if (f.s.Mantissa & 0x1000) // Check for rounding + o.u++; // Round, might overflow to inf, this is OK + } + } + + o.s.Sign = f.s.Sign; + + half ret; + ret.value = (*reinterpret_cast(&o)); + + return ret; +} + +half float_to_half_full_le(float _f) { + FP32le f; + f.f = _f; + float16le o = {0}; + + // Based on ISPC reference code (with minor modifications) + if (f.s.Exponent == 0) // Signed zero/denormal (which will underflow) + o.s.Exponent = 0; + else if (f.s.Exponent == 255) // Inf or NaN (all exponent bits set) + { + o.s.Exponent = 31; + o.s.Mantissa = f.s.Mantissa ? 0x200 : 0; // NaN->qNaN and Inf->Inf + } else // Normalized number + { + // Exponent unbias the single, then bias the halfp + int newexp = f.s.Exponent - 127 + 15; + if (newexp >= 31) // Overflow, return signed infinity + o.s.Exponent = 31; + else if (newexp <= 0) // Underflow + { + if ((14 - newexp) <= 24) // Mantissa might be non-zero + { + unsigned int mant = f.s.Mantissa | 0x800000; // Hidden 1 bit + o.s.Mantissa = mant >> (14 - newexp); + if ((mant >> (13 - newexp)) & 1) // Check for rounding + o.u++; // Round, might overflow into exp bit, but this is OK + } + } else { + o.s.Exponent = static_cast(newexp); + o.s.Mantissa = f.s.Mantissa >> 13; + if (f.s.Mantissa & 0x1000) // Check for rounding + o.u++; // Round, might overflow to inf, this is OK + } + } + + o.s.Sign = f.s.Sign; + + half ret; + ret.value = (*reinterpret_cast(&o)); + return ret; +} + +} // namespace + +float half_to_float(half h) { + // TODO: Compile time detection of endianness + HostEndianness endian; + + if (endian.isBig()) { + float16be f; + f.u = h.value; + return half_to_float_be(f); + } else if (endian.isLittle()) { + float16le f; + f.u = h.value; + return half_to_float_le(f); + } + + ///??? + return std::numeric_limits::quiet_NaN(); +} + +half float_to_half_full(float _f) { + // TODO: Compile time detection of endianness + HostEndianness endian; + + if (endian.isBig()) { + return float_to_half_full_be(_f); + } else if (endian.isLittle()) { + return float_to_half_full_le(_f); + } + + ///??? + half fp16{0}; // TODO: Raise exception or return NaN + return fp16; +} + +matrix2f::matrix2f(const matrix2d &src) { + (*this) = src; +} + +matrix2f &matrix2f::operator=(const matrix2d &src) { + + for (size_t j = 0; j < 2; j++) { + for (size_t i = 0; i < 2; i++) { + m[j][i] = float(src.m[j][i]); + } + } + + return *this; +} + +matrix3f::matrix3f(const matrix3d &src) { + (*this) = src; +} + +matrix3f &matrix3f::operator=(const matrix3d &src) { + + for (size_t j = 0; j < 3; j++) { + for (size_t i = 0; i < 3; i++) { + m[j][i] = float(src.m[j][i]); + } + } + + return *this; +} + +matrix4f::matrix4f(const matrix4d &src) { + (*this) = src; +} + +matrix4f &matrix4f::operator=(const matrix4d &src) { + + for (size_t j = 0; j < 4; j++) { + for (size_t i = 0; i < 4; i++) { + m[j][i] = float(src.m[j][i]); + } + } + + return *this; +} + +matrix2d &matrix2d::operator=(const matrix2f &src) { + + for (size_t j = 0; j < 2; j++) { + for (size_t i = 0; i < 2; i++) { + m[j][i] = double(src.m[j][i]); + } + } + + return *this; +} + +matrix3d &matrix3d::operator=(const matrix3f &src) { + + for (size_t j = 0; j < 3; j++) { + for (size_t i = 0; i < 3; i++) { + m[j][i] = double(src.m[j][i]); + } + } + + return *this; +} + +matrix4d &matrix4d::operator=(const matrix4f &src) { + + for (size_t j = 0; j < 4; j++) { + for (size_t i = 0; i < 4; i++) { + m[j][i] = double(src.m[j][i]); + } + } + + return *this; +} + + +size_t Value::array_size() const { + if (!is_array()) { + return 0; + } + + // primvar types only. + +#define APPLY_FUNC_TO_TYPES(__FUNC) \ + __FUNC(bool) \ + __FUNC(value::token) \ + __FUNC(std::string) \ + __FUNC(StringData) \ + __FUNC(half) \ + __FUNC(half2) \ + __FUNC(half3) \ + __FUNC(half4) \ + __FUNC(int32_t) \ + __FUNC(uint32_t) \ + __FUNC(int2) \ + __FUNC(int3) \ + __FUNC(int4) \ + __FUNC(uint2) \ + __FUNC(uint3) \ + __FUNC(uint4) \ + __FUNC(int64_t) \ + __FUNC(uint64_t) \ + __FUNC(float) \ + __FUNC(float2) \ + __FUNC(float3) \ + __FUNC(float4) \ + __FUNC(double) \ + __FUNC(double2) \ + __FUNC(double3) \ + __FUNC(double4) \ + __FUNC(quath) \ + __FUNC(quatf) \ + __FUNC(quatd) \ + __FUNC(normal3h) \ + __FUNC(normal3f) \ + __FUNC(normal3d) \ + __FUNC(vector3h) \ + __FUNC(vector3f) \ + __FUNC(vector3d) \ + __FUNC(point3h) \ + __FUNC(point3f) \ + __FUNC(point3d) \ + __FUNC(color3f) \ + __FUNC(color3d) \ + __FUNC(color4h) \ + __FUNC(color4f) \ + __FUNC(color4d) \ + __FUNC(texcoord2h) \ + __FUNC(texcoord2f) \ + __FUNC(texcoord2d) \ + __FUNC(texcoord3h) \ + __FUNC(texcoord3f) \ + __FUNC(texcoord3d) \ + __FUNC(matrix2d) \ + __FUNC(matrix3d) \ + __FUNC(matrix4d) \ + __FUNC(frame4d) + +#define ARRAY_SIZE_GET(__ty) case value::TypeTraits<__ty>::type_id() | value::TYPE_ID_1D_ARRAY_BIT: { \ + if (auto pv = v_.cast>()) { \ + return pv->size(); \ + } \ + return 0; \ + } + + + switch (v_.type_id()) { + APPLY_FUNC_TO_TYPES(ARRAY_SIZE_GET) + default: + return 0; + } + +#undef ARRAY_SIZE_GET +#undef APPLY_FUNC_TO_TYPES + +} + +bool RoleTypeCast(const uint32_t roleTyId, value::Value &inout) { + const uint32_t srcUnderlyingTyId = inout.underlying_type_id(); + + DCOUT("input type = " << inout.type_name()); + + // scalar and array +#define ROLE_TYPE_CAST(__roleTy, __srcBaseTy) \ + { \ + static_assert(value::TypeTraits<__roleTy>::size() == \ + value::TypeTraits<__srcBaseTy>::size(), \ + ""); \ + if (srcUnderlyingTyId == value::TypeTraits<__srcBaseTy>::type_id()) { \ + if (roleTyId == value::TypeTraits<__roleTy>::type_id()) { \ + if (auto pv = inout.get_value<__srcBaseTy>()) { \ + __srcBaseTy val = pv.value(); \ + __roleTy newval; \ + memcpy(&newval, &val, sizeof(__srcBaseTy)); \ + inout = newval; \ + return true; \ + } \ + } \ + } else if (srcUnderlyingTyId == \ + (value::TypeTraits<__srcBaseTy>::type_id() | \ + value::TYPE_ID_1D_ARRAY_BIT)) { \ + if (roleTyId == value::TypeTraits>::type_id()) { \ + if (auto pv = inout.get_value>()) { \ + std::vector<__srcBaseTy> val = pv.value(); \ + std::vector<__roleTy> newval; \ + newval.resize(val.size()); \ + memcpy(newval.data(), val.data(), sizeof(__srcBaseTy) * val.size()); \ + inout = newval; \ + return true; \ + } \ + } \ + } \ + } + + ROLE_TYPE_CAST(value::texcoord2h, value::half2) + ROLE_TYPE_CAST(value::texcoord2f, value::float2) + ROLE_TYPE_CAST(value::texcoord2d, value::double2) + + ROLE_TYPE_CAST(value::texcoord3h, value::half3) + ROLE_TYPE_CAST(value::texcoord3f, value::float3) + ROLE_TYPE_CAST(value::texcoord3d, value::double3) + + ROLE_TYPE_CAST(value::normal3h, value::half3) + ROLE_TYPE_CAST(value::normal3f, value::float3) + ROLE_TYPE_CAST(value::normal3d, value::double3) + + ROLE_TYPE_CAST(value::vector3h, value::half3) + ROLE_TYPE_CAST(value::vector3f, value::float3) + ROLE_TYPE_CAST(value::vector3d, value::double3) + + ROLE_TYPE_CAST(value::point3h, value::half3) + ROLE_TYPE_CAST(value::point3f, value::float3) + ROLE_TYPE_CAST(value::point3d, value::double3) + + ROLE_TYPE_CAST(value::color3h, value::half3) + ROLE_TYPE_CAST(value::color3f, value::float3) + ROLE_TYPE_CAST(value::color3d, value::double3) + + ROLE_TYPE_CAST(value::color4h, value::half4) + ROLE_TYPE_CAST(value::color4f, value::float4) + ROLE_TYPE_CAST(value::color4d, value::double4) + + ROLE_TYPE_CAST(value::frame4d, value::matrix4d) + +#undef ROLE_TYPE_CAST + + return false; +} + +// TODO: Use template +bool UpcastType(const std::string &reqType, value::Value &inout) { + // `reqType` may be Role type. Get underlying type + uint32_t tyid; + if (auto pv = value::TryGetUnderlyingTypeId(reqType)) { + tyid = pv.value(); + } else { + // Invalid reqType. + return false; + } + + bool reqTypeArray = false; + //uint32_t baseReqTyId; + DCOUT("UpcastType trial: reqTy : " << reqType + << ", valtype = " << inout.type_name()); + + if (endsWith(reqType, "[]")) { + reqTypeArray = true; + //baseReqTyId = value::GetTypeId(removeSuffix(reqType, "[]")); + } else { + //baseReqTyId = value::GetTypeId(reqType); + } + DCOUT("is array: " << reqTypeArray); + + // For array + if (reqTypeArray) { + // TODO + } else { + if (tyid == value::TYPE_ID_FLOAT) { + float dst; + if (auto pv = inout.get_value()) { + dst = half_to_float(pv.value()); + inout = dst; + return true; + } + } else if (tyid == value::TYPE_ID_FLOAT2) { + if (auto pv = inout.get_value()) { + value::float2 dst; + value::half2 v = pv.value(); + dst[0] = half_to_float(v[0]); + dst[1] = half_to_float(v[1]); + inout = dst; + return true; + } + + } else if (tyid == value::TYPE_ID_FLOAT3) { + value::float3 dst; + if (auto pv = inout.get_value()) { + value::half3 v = pv.value(); + dst[0] = half_to_float(v[0]); + dst[1] = half_to_float(v[1]); + dst[2] = half_to_float(v[2]); + inout = dst; + return true; + } + } else if (tyid == value::TYPE_ID_FLOAT4) { + value::float4 dst; + if (auto pv = inout.get_value()) { + value::half4 v = pv.value(); + dst[0] = half_to_float(v[0]); + dst[1] = half_to_float(v[1]); + dst[2] = half_to_float(v[2]); + dst[3] = half_to_float(v[3]); + inout = dst; + return true; + } + } else if (tyid == value::TYPE_ID_DOUBLE) { + double dst; + if (auto pv = inout.get_value()) { + dst = double(half_to_float(pv.value())); + inout = dst; + return true; + } + } else if (tyid == value::TYPE_ID_DOUBLE2) { + value::double2 dst; + if (auto pv = inout.get_value()) { + value::half2 v = pv.value(); + dst[0] = double(half_to_float(v[0])); + dst[1] = double(half_to_float(v[1])); + inout = dst; + return true; + } + } else if (tyid == value::TYPE_ID_DOUBLE3) { + value::double3 dst; + if (auto pv = inout.get_value()) { + value::half3 v = pv.value(); + dst[0] = double(half_to_float(v[0])); + dst[1] = double(half_to_float(v[1])); + dst[2] = double(half_to_float(v[2])); + inout = dst; + return true; + } + } else if (tyid == value::TYPE_ID_DOUBLE4) { + value::double4 dst; + if (auto pv = inout.get_value()) { + value::half4 v = pv.value(); + dst[0] = double(half_to_float(v[0])); + dst[1] = double(half_to_float(v[1])); + dst[2] = double(half_to_float(v[2])); + dst[3] = double(half_to_float(v[3])); + inout = dst; + return true; + } + } + } + + return false; +} + +#if 0 +bool FlexibleTypeCast(const value::Value &src, value::Value &dst) { + uint32_t src_utype_id = src.type_id(); + uint32_t dst_utype_id = src.type_id(); + + if (src_utype_id == value::TypeTraits::type_id()) { + + } + + // TODO + + return false; +} +#endif + +} // namespace value +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/value-types.hh b/contrib/tinyusdz/tinyusdz_repo/src/value-types.hh new file mode 100644 index 000000000..bb4ab78c8 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/value-types.hh @@ -0,0 +1,2705 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2021 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. + +/// +/// Type-erasure technique for Value, a Value class which can represent USD's +/// mandatory and frequently used types(e.g. `float3`, `token`, `asset`) and its +/// array and compound-types(1D/2D array, dictionary). Neigher std::any nor +/// std::variant is applicable for such usecases, so write our own. +/// +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +// TODO(syoyo): Use C++17 std::optional when compiled with C++-17 compiler + +// clang and gcc +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) + +#ifdef nsel_CONFIG_NO_EXCEPTIONS +#undef nsel_CONFIG_NO_EXCEPTIONS +#endif +#ifdef nssv_CONFIG_NO_EXCEPTIONS +#undef nssv_CONFIG_NO_EXCEPTIONS +#endif + +#define nsel_CONFIG_NO_EXCEPTIONS 0 +#define nssv_CONFIG_NO_EXCEPTIONS 0 +#else +// -fno-exceptions +#if !defined(nsel_CONFIG_NO_EXCEPTIONS) +#define nsel_CONFIG_NO_EXCEPTIONS 1 +#endif + +#define nssv_CONFIG_NO_EXCEPTIONS 1 +#endif +//#include "../../src/nonstd/expected.hpp" +#include "nonstd/optional.hpp" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include "token-type.hh" +#include "common-macros.inc" + +// forward decl +namespace linb { +class any; +}; + +namespace tinyusdz { + +namespace value { + +// Identifier is the one used in USDA(Ascii) +// See: https://graphics.pixar.com/usd/release/api/_usd__page__datatypes.html +// NOTE: There are some TinyUSDZ specific types(e.g., short, char) +constexpr auto kToken = "token"; +constexpr auto kString = "string"; +constexpr auto kPath = + "Path"; // generic(usually prim) path. internal representation. +constexpr auto kAssetPath = "asset"; // `asset` in USDA +constexpr auto kDictionary = "dictionary"; +constexpr auto kTimeCode = "timecode"; + +constexpr auto kBool = "bool"; +constexpr auto kChar = "char"; +constexpr auto kChar2 = "char2"; +constexpr auto kChar3 = "char3"; +constexpr auto kChar4 = "char4"; +constexpr auto kUChar = "uchar"; +constexpr auto kUChar2 = "uchar2"; +constexpr auto kUChar3 = "uchar3"; +constexpr auto kUChar4 = "uchar4"; +constexpr auto kHalf = "half"; +constexpr auto kInt = "int"; +constexpr auto kUInt = "uint"; +constexpr auto kInt64 = "int64"; +constexpr auto kUInt64 = "uint64"; + +constexpr auto kShort = "short"; +constexpr auto kShort2 = "short2"; +constexpr auto kShort3 = "short3"; +constexpr auto kShort4 = "short4"; + +constexpr auto kUShort = "ushort"; +constexpr auto kUShort2 = "ushort2"; +constexpr auto kUShort3 = "ushort3"; +constexpr auto kUShort4 = "ushort4"; + +constexpr auto kInt2 = "int2"; +constexpr auto kInt3 = "int3"; +constexpr auto kInt4 = "int4"; + +constexpr auto kUInt2 = "uint2"; +constexpr auto kUInt3 = "uint3"; +constexpr auto kUInt4 = "uint4"; + +constexpr auto kHalf2 = "half2"; +constexpr auto kHalf3 = "half3"; +constexpr auto kHalf4 = "half4"; + +// Seems primarily used in usdSkel. +// float precision matrix is not directly used in XformOp +constexpr auto kMatrix2f = "matrix2f"; +constexpr auto kMatrix3f = "matrix3f"; +constexpr auto kMatrix4f = "matrix4f"; + +constexpr auto kMatrix2d = "matrix2d"; +constexpr auto kMatrix3d = "matrix3d"; +constexpr auto kMatrix4d = "matrix4d"; + +constexpr auto kFloat = "float"; +constexpr auto kFloat2 = "float2"; +constexpr auto kFloat3 = "float3"; +constexpr auto kFloat4 = "float4"; + +constexpr auto kDouble = "double"; +constexpr auto kDouble2 = "double2"; +constexpr auto kDouble3 = "double3"; +constexpr auto kDouble4 = "double4"; + +constexpr auto kQuath = "quath"; +constexpr auto kQuatf = "quatf"; +constexpr auto kQuatd = "quatd"; + +constexpr auto kVector3h = "vector3h"; +constexpr auto kVector3f = "vector3f"; +constexpr auto kVector3d = "vector3d"; + +constexpr auto kVector4h = "vector4h"; +constexpr auto kVector4f = "vector4f"; +constexpr auto kVector4d = "vector4d"; + +constexpr auto kPoint3h = "point3h"; +constexpr auto kPoint3f = "point3f"; +constexpr auto kPoint3d = "point3d"; + +constexpr auto kNormal3h = "normal3h"; +constexpr auto kNormal3f = "normal3f"; +constexpr auto kNormal3d = "normal3d"; + +constexpr auto kColor3h = "color3h"; +constexpr auto kColor3f = "color3f"; +constexpr auto kColor3d = "color3d"; +constexpr auto kColor4h = "color4h"; +constexpr auto kColor4f = "color4f"; +constexpr auto kColor4d = "color4d"; + +constexpr auto kFrame4d = "frame4d"; + +constexpr auto kTexCoord2h = "texCoord2h"; +constexpr auto kTexCoord2f = "texCoord2f"; +constexpr auto kTexCoord2d = "texCoord2d"; + +constexpr auto kTexCoord3h = "texCoord3h"; +constexpr auto kTexCoord3f = "texCoord3f"; +constexpr auto kTexCoord3d = "texCoord3d"; + +constexpr auto kTexCoord4h = "texCoord4h"; +constexpr auto kTexCoord4f = "texCoord4f"; +constexpr auto kTexCoord4d = "texCoord4d"; + +constexpr auto kRelationship = "rel"; + +inline std::string Add1DArraySuffix(const std::string &c) { return c + "[]"; } + +using token = tinyusdz::Token; + +// single or triple-quoted('"""' or ''') string +struct StringData { + + StringData() = default; + StringData(const std::string &v) : value(v) {} + StringData &operator=(const std::string &v) { + value = v; + return (*this); + } + + std::string value; + bool is_triple_quoted{false}; + bool single_quote{false}; // true for ', false for " + + // optional(for USDA) + int line_row{0}; + int line_col{0}; +}; + +// SdfAssetPath +class AssetPath { + public: + AssetPath() = default; + AssetPath(const std::string &a) : asset_path_(a) {} + AssetPath(const std::string &a, const std::string &r) + : asset_path_(a), resolved_path_(r) {} + + bool Resolve() { + // TODO; + return false; + } + + const std::string &GetAssetPath() const { return asset_path_; } + + const std::string GetResolvedPath() const { return resolved_path_; } + + private: + std::string asset_path_; + std::string resolved_path_; +}; + +class TimeCode { + public: + TimeCode(const double d) : time_(d) {} + + static constexpr double Default() { + // Return qNaN. same in pxrUSD + return std::numeric_limits::quiet_NaN(); + } + + double Get(bool *is_default_timecode) { + if (is_default_timecode) { + (*is_default_timecode) = is_default(); + } + return time_; + } + + bool is_default() { + // TODO: Bitwise comparison + return !std::isnan(time_); + } + + private: + double time_; +}; + +static_assert(sizeof(TimeCode) == 8, "Size of TimeCode must be 8."); + +// +// Type ID for TypeTraits::type_id. +// +// These type IDs are internally used and can be changed arbitrary. +// +// These ID assignment won't affect Crate binary serialization. +// (See `crate-format.hh` for Type ID used in Crate binary) +// +// TODO(syoyo): Support 3D and 4D? +constexpr uint32_t TYPE_ID_1D_ARRAY_BIT = 1 << 20; // 1024 +// constexpr uint32_t TYPE_ID_2D_ARRAY_BIT = 1 << 21; // 2048 +// constexpr uint32_t TYPE_ID_3D_ARRAY_BIT = 1 << 22; +// constexpr uint32_t TYPE_ID_4D_ARRAY_BIT = 1 << 23; +constexpr uint32_t TYPE_ID_TERMINATOR_BIT = 1 << 24; + +enum TypeId { + TYPE_ID_INVALID, // = 0 + TYPE_ID_NULL, + TYPE_ID_VOID, + TYPE_ID_MONOSTATE, + TYPE_ID_VALUEBLOCK, // Value block. `None` in ascii. + + // -- begin value type + TYPE_ID_VALUE_BEGIN, + + TYPE_ID_TOKEN, + TYPE_ID_STRING, + TYPE_ID_STRING_DATA, // String for primvar and metadata. Includes multi-line + // string + + TYPE_ID_BOOL, + + TYPE_ID_CHAR, + TYPE_ID_CHAR2, + TYPE_ID_CHAR3, + TYPE_ID_CHAR4, + + // TYPE_ID_INT8, + TYPE_ID_HALF, + TYPE_ID_INT32, + TYPE_ID_INT64, + + TYPE_ID_HALF2, + TYPE_ID_HALF3, + TYPE_ID_HALF4, + + TYPE_ID_INT2, // int32 x 2 + TYPE_ID_INT3, + TYPE_ID_INT4, + + TYPE_ID_UCHAR, // uint8 + TYPE_ID_UCHAR2, + TYPE_ID_UCHAR3, + TYPE_ID_UCHAR4, + + TYPE_ID_UINT32, + TYPE_ID_UINT64, + + TYPE_ID_SHORT, + TYPE_ID_SHORT2, + TYPE_ID_SHORT3, + TYPE_ID_SHORT4, + + TYPE_ID_USHORT, + TYPE_ID_USHORT2, + TYPE_ID_USHORT3, + TYPE_ID_USHORT4, + + TYPE_ID_UINT2, + TYPE_ID_UINT3, + TYPE_ID_UINT4, + + TYPE_ID_FLOAT, + TYPE_ID_FLOAT2, + TYPE_ID_FLOAT3, + TYPE_ID_FLOAT4, + + TYPE_ID_DOUBLE, + TYPE_ID_DOUBLE2, + TYPE_ID_DOUBLE3, + TYPE_ID_DOUBLE4, + + TYPE_ID_QUATH, + TYPE_ID_QUATF, + TYPE_ID_QUATD, + + TYPE_ID_MATRIX2F, + TYPE_ID_MATRIX3F, + TYPE_ID_MATRIX4F, + + TYPE_ID_MATRIX2D, + TYPE_ID_MATRIX3D, + TYPE_ID_MATRIX4D, + + TYPE_ID_COLOR3H, + TYPE_ID_COLOR3F, + TYPE_ID_COLOR3D, + + TYPE_ID_COLOR4H, + TYPE_ID_COLOR4F, + TYPE_ID_COLOR4D, + + TYPE_ID_POINT3H, + TYPE_ID_POINT3F, + TYPE_ID_POINT3D, + + TYPE_ID_NORMAL3H, + TYPE_ID_NORMAL3F, + TYPE_ID_NORMAL3D, + + TYPE_ID_VECTOR3H, + TYPE_ID_VECTOR3F, + TYPE_ID_VECTOR3D, + + TYPE_ID_FRAME4D, + + TYPE_ID_TEXCOORD2H, + TYPE_ID_TEXCOORD2F, + TYPE_ID_TEXCOORD2D, + + TYPE_ID_TEXCOORD3H, + TYPE_ID_TEXCOORD3F, + TYPE_ID_TEXCOORD3D, + + TYPE_ID_EXTENT, // float3[2] + + TYPE_ID_TIMECODE, + + // TYPE_ID_ASSET, + TYPE_ID_ASSET_PATH, + + TYPE_ID_DICT, // Generic dict type. TODO: remove? + TYPE_ID_CUSTOMDATA, // similar to `dictionary`, but limited types are allowed + // to use. for metadatum(e.g. `customData` in Prim Meta) + + TYPE_ID_VALUE_END, + + // -- end value type + + TYPE_ID_LAYER_OFFSET, + TYPE_ID_PAYLOAD, + + // Types in prim-types.hh + TYPE_ID_REFERENCE, + TYPE_ID_SPECIFIER, + TYPE_ID_PERMISSION, + TYPE_ID_VARIABILITY, + TYPE_ID_LIST_OP_TOKEN, + TYPE_ID_LIST_OP_STRING, + TYPE_ID_LIST_OP_PATH, + TYPE_ID_LIST_OP_REFERENCE, + TYPE_ID_LIST_OP_INT, + TYPE_ID_LIST_OP_INT64, + TYPE_ID_LIST_OP_UINT, + TYPE_ID_LIST_OP_UINT64, + TYPE_ID_LIST_OP_PAYLOAD, + + TYPE_ID_PATH, + TYPE_ID_PATH_VECTOR, + TYPE_ID_TOKEN_VECTOR, + TYPE_ID_RELATIONSHIP, + + // -- end of base type for Property. + + TYPE_ID_TIMESAMPLES, + TYPE_ID_VARIANT_SELECION_MAP, + + // Types in crate-format.hh + TYPE_ID_CRATE_BEGIN = 256, + TYPE_ID_CRATE_VALUE, + TYPE_ID_CRATE_UNREGISTERED_VALUE, + TYPE_ID_CRATE_LIST_OP_UNREGISTERED_VALUE, + TYPE_ID_CRATE_END, + + // Types for Model and GPrim + TYPE_ID_MODEL_BEGIN = (1 << 10), + TYPE_ID_MODEL, // internally used class + // TYPE_ID_GROUP, + TYPE_ID_SCOPE, + TYPE_ID_GPRIM, + TYPE_ID_GEOM_XFORM, + TYPE_ID_GEOM_MESH, + TYPE_ID_GEOM_BASIS_CURVES, + TYPE_ID_GEOM_NURBS_CURVES, + TYPE_ID_GEOM_SPHERE, + TYPE_ID_GEOM_CUBE, + TYPE_ID_GEOM_CYLINDER, + TYPE_ID_GEOM_CONE, + TYPE_ID_GEOM_CAPSULE, + TYPE_ID_GEOM_POINTS, + TYPE_ID_GEOM_GEOMSUBSET, + TYPE_ID_GEOM_POINT_INSTANCER, + TYPE_ID_GEOM_CAMERA, + TYPE_ID_GEOM_END, + + // Types for usdLux + TYPE_ID_LUX_BEGIN = (1 << 10) + (1 << 9), + TYPE_ID_LUX_SPHERE, + TYPE_ID_LUX_DOME, + TYPE_ID_LUX_CYLINDER, + TYPE_ID_LUX_DISK, + TYPE_ID_LUX_RECT, + TYPE_ID_LUX_DISTANT, + TYPE_ID_LUX_GEOMETRY, + TYPE_ID_LUX_PORTAL, + TYPE_ID_LUX_PLUGIN, + TYPE_ID_LUX_END, + + // Types for usdShader + TYPE_ID_SHADER_BEGIN = 1 << 11, + TYPE_ID_SHADER, + TYPE_ID_MATERIAL, + TYPE_ID_NODEGRAPH, + TYPE_ID_SHADER_END, + + // Types for usdImaging and usdMtlx + // See /pxr/usdImaging/usdImaging/tokens.h + TYPE_ID_IMAGING_BEGIN = (1 << 11) + (1 << 10), + TYPE_ID_IMAGING_SHADER_NODE, + TYPE_ID_IMAGING_PREVIEWSURFACE, + TYPE_ID_IMAGING_UVTEXTURE, + TYPE_ID_IMAGING_PRIMVAR_READER_FLOAT, + TYPE_ID_IMAGING_PRIMVAR_READER_FLOAT2, + TYPE_ID_IMAGING_PRIMVAR_READER_FLOAT3, + TYPE_ID_IMAGING_PRIMVAR_READER_FLOAT4, + TYPE_ID_IMAGING_PRIMVAR_READER_INT, + TYPE_ID_IMAGING_PRIMVAR_READER_STRING, + TYPE_ID_IMAGING_PRIMVAR_READER_NORMAL, // float3 + TYPE_ID_IMAGING_PRIMVAR_READER_POINT, // float3 + TYPE_ID_IMAGING_PRIMVAR_READER_VECTOR, // float3 + TYPE_ID_IMAGING_PRIMVAR_READER_MATRIX, // float3 + TYPE_ID_IMAGING_TRANSFORM_2D, + + TYPE_ID_IMAGING_MTLX_PREVIEWSURFACE, + TYPE_ID_IMAGING_MTLX_STANDARDSURFACE, + + TYPE_ID_IMAGING_END, + + // Types for usdVol + TYPE_ID_VOL_BEGIN = 1 << 12, + TYPE_ID_VOL_END, + + // Types for usdSkel + TYPE_ID_SKEL_BEGIN = 1 << 13, + TYPE_ID_SKEL_ROOT, + TYPE_ID_SKELETON, + TYPE_ID_SKELANIMATION, + TYPE_ID_BLENDSHAPE, + TYPE_ID_SKEL_END, + + TYPE_ID_MODEL_END, + + + // Types for API + TYPE_ID_API_BEGIN = 1 << 14, + TYPE_ID_COLLECTION, + TYPE_ID_COLLECTION_INSTANCE, + TYPE_ID_MATERIAL_BINDING, + TYPE_ID_API_END, + + // Base ID for user data type(less than `TYPE_ID_1D_ARRAY_BIT-1`) + TYPE_ID_USER_BEGIN = 1 << 16, + + TYPE_ID_ALL = (TYPE_ID_TERMINATOR_BIT - 1) // terminator. +}; + +struct timecode { + double value; +}; + +struct half { + uint16_t value; +}; + +using half2 = std::array; +using half3 = std::array; +using half4 = std::array; + +float half_to_float(value::half h); +half float_to_half_full(float f); + +inline half operator+(const half &a, const half &b) { + return float_to_half_full(half_to_float(a) + half_to_float(b)); +} + +inline half operator-(const half &a, const half &b) { + return float_to_half_full(half_to_float(a) - half_to_float(b)); +} + +inline half operator*(const half &a, const half &b) { + return float_to_half_full(half_to_float(a) * half_to_float(b)); +} + +// TODO: save div +inline half operator/(const half &a, const half &b) { + return float_to_half_full(half_to_float(a) / half_to_float(b)); +} + +inline half& operator+=(half &a, const half &b) { + a = float_to_half_full(half_to_float(a) + half_to_float(b)); + return a; +} + +inline half& operator-=(half &a, const half &b) { + a = float_to_half_full(half_to_float(a) - half_to_float(b)); + return a; +} + +inline half& operator*=(half &a, const half &b) { + a = float_to_half_full(half_to_float(a) * half_to_float(b)); + return a; +} + +// TODO: save div +inline half& operator/=(half &a, const half &b) { + a = float_to_half_full(half_to_float(a) / half_to_float(b)); + return a; +} + +inline half operator+(const half &a, float b) { + return float_to_half_full(half_to_float(a) + b); +} + +inline half operator-(const half &a, float b) { + return float_to_half_full(half_to_float(a) - b); +} + +inline half operator*(const half &a, float b) { + return float_to_half_full(half_to_float(a) * b); +} + +inline half operator/(const half &a, float b) { + return float_to_half_full(half_to_float(a) / b); +} + +inline half operator+(float a, const half &b) { + return float_to_half_full(a + half_to_float(b)); +} + +inline half operator-(float a, const half &b) { + return float_to_half_full(a - half_to_float(b)); +} + +inline half operator*(float a, const half &b) { + return float_to_half_full(a * half_to_float(b)); +} + +inline half operator/(float a, const half &b) { + return float_to_half_full(a / half_to_float(b)); +} + +using char2 = std::array; +using char3 = std::array; +using char4 = std::array; + +using uchar2 = std::array; +using uchar3 = std::array; +using uchar4 = std::array; + +using short2 = std::array; +using short3 = std::array; +using short4 = std::array; + +using ushort2 = std::array; +using ushort3 = std::array; +using ushort4 = std::array; + +using int2 = std::array; +using int3 = std::array; +using int4 = std::array; + +using uint2 = std::array; +using uint3 = std::array; +using uint4 = std::array; + +using float2 = std::array; +using float3 = std::array; +using float4 = std::array; + +using double2 = std::array; +using double3 = std::array; +using double4 = std::array; + +// +// Matrix is represented as row-major order as done in pxrUSD. +// m[i][j] is read as: i'th row, j'th column +// memory layout is same both for column-major and row-major. +// (e.g. m[3][0], m[3][1], m[3][2] or a[13], a[14], a[15] are translation components for 4x4 matrix) +// + +struct matrix2d; +struct matrix3d; +struct matrix4d; + +struct matrix2f { + matrix2f() { + m[0][0] = 1.0f; + m[0][1] = 0.0f; + + m[1][0] = 0.0f; + m[1][1] = 1.0f; + } + + matrix2f(const std::array &arr) { + m[0][0] = arr[0]; + m[0][1] = arr[1]; + m[1][0] = arr[2]; + m[1][1] = arr[3]; + } + + inline void set_row(uint32_t row, float x, float y) { + if (row < 2) { + m[row][0] = x; + m[row][1] = y; + } + } + + inline void set_scale(float sx, float sy) { + m[0][0] = sx; + m[0][1] = 0.0f; + + m[1][0] = 0.0f; + m[1][1] = sy; + } + + static matrix2f identity() { + matrix2f m; + + m.m[0][0] = 1.0f; + m.m[0][1] = 0.0f; + + m.m[1][0] = 0.0f; + m.m[1][1] = 1.0f; + + return m; + } + + matrix2f(const matrix2d &rhs); + matrix2f &operator=(const matrix2d &rhs); + + float m[2][2]; +}; + +struct matrix3f { + matrix3f() { + m[0][0] = 1.0f; + m[0][1] = 0.0f; + m[0][2] = 0.0f; + + m[1][0] = 0.0f; + m[1][1] = 1.0f; + m[1][2] = 0.0f; + + m[2][0] = 0.0f; + m[2][1] = 0.0f; + m[2][2] = 1.0f; + } + + matrix3f(const std::array &arr) { + m[0][0] = arr[0]; + m[0][1] = arr[1]; + m[0][2] = arr[2]; + m[1][0] = arr[3]; + m[1][1] = arr[4]; + m[1][2] = arr[5]; + m[2][0] = arr[6]; + m[2][1] = arr[7]; + m[2][2] = arr[8]; + } + + inline void set_row(uint32_t row, float x, float y, float z) { + if (row < 3) { + m[row][0] = x; + m[row][1] = y; + m[row][2] = z; + } + } + + inline void set_scale(float sx, float sy, float sz) { + m[0][0] = sx; + m[0][1] = 0.0f; + m[0][2] = 0.0f; + + m[1][0] = 0.0f; + m[1][1] = sy; + m[1][2] = 0.0f; + + m[2][0] = 0.0f; + m[2][1] = 0.0f; + m[2][2] = sz; + + } + + inline void set_translation(float tx, float ty, float tz) { + m[2][0] = tx; + m[2][1] = ty; + m[2][2] = tz; + } + + static matrix3f identity() { + matrix3f m; + + m.m[0][0] = 1.0f; + m.m[0][1] = 0.0f; + m.m[0][2] = 0.0f; + + m.m[1][0] = 0.0f; + m.m[1][1] = 1.0f; + m.m[1][2] = 0.0f; + + m.m[2][0] = 0.0f; + m.m[2][1] = 0.0f; + m.m[2][2] = 1.0f; + + return m; + } + + matrix3f(const matrix3d &rhs); + matrix3f &operator=(const matrix3d &rhs); + + float m[3][3]; +}; + +struct matrix4f { + matrix4f() { + m[0][0] = 1.0f; + m[0][1] = 0.0f; + m[0][2] = 0.0f; + m[0][3] = 0.0f; + + m[1][0] = 0.0f; + m[1][1] = 1.0f; + m[1][2] = 0.0f; + m[1][3] = 0.0f; + + m[2][0] = 0.0f; + m[2][1] = 0.0f; + m[2][2] = 1.0f; + m[2][3] = 0.0f; + + m[3][0] = 0.0f; + m[3][1] = 0.0f; + m[3][2] = 0.0f; + m[3][3] = 1.0f; + } + + matrix4f(const std::array &arr) { + m[0][0] = arr[0]; + m[0][1] = arr[1]; + m[0][2] = arr[2]; + m[0][3] = arr[3]; + m[1][0] = arr[4]; + m[1][1] = arr[5]; + m[1][2] = arr[6]; + m[1][3] = arr[7]; + m[2][0] = arr[8]; + m[2][1] = arr[9]; + m[2][2] = arr[10]; + m[2][3] = arr[11]; + m[3][0] = arr[12]; + m[3][1] = arr[13]; + m[3][2] = arr[14]; + m[3][3] = arr[15]; + } + + inline void set_row(uint32_t row, float x, float y, float z, float w) { + if (row < 4) { + m[row][0] = x; + m[row][1] = y; + m[row][2] = z; + m[row][3] = w; + } + } + + inline void set_scale(float sx, float sy, float sz) { + m[0][0] = sx; + m[0][1] = 0.0f; + m[0][2] = 0.0f; + m[0][3] = 0.0f; + + m[1][0] = 0.0f; + m[1][1] = sy; + m[1][2] = 0.0f; + m[1][3] = 0.0f; + + m[2][0] = 0.0f; + m[2][1] = 0.0f; + m[2][2] = sz; + m[2][3] = 0.0f; + + m[3][0] = 0.0f; + m[3][1] = 0.0f; + m[3][2] = 0.0f; + m[3][3] = 1.0f; + } + + inline void set_translation(float tx, float ty, float tz) { + m[3][0] = tx; + m[3][1] = ty; + m[3][2] = tz; + } + + static matrix4f identity() { + matrix4f m; + + m.m[0][0] = 1.0f; + m.m[0][1] = 0.0f; + m.m[0][2] = 0.0f; + m.m[0][3] = 0.0f; + + m.m[1][0] = 0.0f; + m.m[1][1] = 1.0f; + m.m[1][2] = 0.0f; + m.m[1][3] = 0.0f; + + m.m[2][0] = 0.0f; + m.m[2][1] = 0.0f; + m.m[2][2] = 1.0f; + m.m[2][3] = 0.0f; + + m.m[3][0] = 0.0f; + m.m[3][1] = 0.0f; + m.m[3][2] = 0.0f; + m.m[3][3] = 1.0f; + + return m; + } + + matrix4f(const matrix4d &rhs); + + matrix4f &operator=(const matrix4d &rhs); + + float m[4][4]; +}; + +struct matrix2d { + matrix2d() { + m[0][0] = 1.0; + m[0][1] = 0.0; + + m[1][0] = 0.0; + m[1][1] = 1.0; + } + + matrix2d(const std::array &arr) { + m[0][0] = arr[0]; + m[0][1] = arr[1]; + m[1][0] = arr[2]; + m[1][1] = arr[3]; + } + + inline void set_row(uint32_t row, double x, double y) { + if (row < 2) { + m[row][0] = x; + m[row][1] = y; + } + } + + inline void set_scale(double sx, double sy) { + m[0][0] = sx; + m[0][1] = 0.0; + + m[1][0] = 0.0; + m[1][1] = sy; + } + + static matrix2d identity() { + matrix2d m; + + m.m[0][0] = 1.0; + m.m[0][1] = 0.0; + + m.m[1][0] = 0.0; + m.m[1][1] = 1.0; + + return m; + } + + matrix2d &operator=(const matrix2f &rhs); + + double m[2][2]; +}; + +struct matrix3d { + matrix3d() { + m[0][0] = 1.0; + m[0][1] = 0.0; + m[0][2] = 0.0; + + m[1][0] = 0.0; + m[1][1] = 1.0; + m[1][2] = 0.0; + + m[2][0] = 0.0; + m[2][1] = 0.0; + m[2][2] = 1.0; + } + + matrix3d(const std::array &arr) { + m[0][0] = arr[0]; + m[0][1] = arr[1]; + m[0][2] = arr[2]; + m[1][0] = arr[3]; + m[1][1] = arr[4]; + m[1][2] = arr[5]; + m[2][0] = arr[6]; + m[2][1] = arr[7]; + m[2][2] = arr[8]; + } + + inline void set_row(uint32_t row, double x, double y, double z) { + if (row < 3) { + m[row][0] = x; + m[row][1] = y; + m[row][2] = z; + } + } + + inline void set_scale(double sx, double sy, double sz) { + m[0][0] = sx; + m[0][1] = 0.0; + m[0][2] = 0.0; + + m[1][0] = 0.0; + m[1][1] = sy; + m[1][2] = 0.0; + + m[2][0] = 0.0; + m[2][1] = 0.0; + m[2][2] = sz; + } + + static matrix3d identity() { + matrix3d m; + + m.m[0][0] = 1.0; + m.m[0][1] = 0.0; + m.m[0][2] = 0.0; + + m.m[1][0] = 0.0; + m.m[1][1] = 1.0; + m.m[1][2] = 0.0; + + m.m[2][0] = 0.0; + m.m[2][1] = 0.0; + m.m[2][2] = 1.0; + + return m; + } + + matrix3d &operator=(const matrix3f &rhs); + + double m[3][3]; +}; + +struct matrix4d { + matrix4d() { + m[0][0] = 1.0; + m[0][1] = 0.0; + m[0][2] = 0.0; + m[0][3] = 0.0; + + m[1][0] = 0.0; + m[1][1] = 1.0; + m[1][2] = 0.0; + m[1][3] = 0.0; + + m[2][0] = 0.0; + m[2][1] = 0.0; + m[2][2] = 1.0; + m[2][3] = 0.0; + + m[3][0] = 0.0; + m[3][1] = 0.0; + m[3][2] = 0.0; + m[3][3] = 1.0; + } + + matrix4d(const std::array &arr) { + m[0][0] = arr[0]; + m[0][1] = arr[1]; + m[0][2] = arr[2]; + m[0][3] = arr[3]; + m[1][0] = arr[4]; + m[1][1] = arr[5]; + m[1][2] = arr[6]; + m[1][3] = arr[7]; + m[2][0] = arr[8]; + m[2][1] = arr[9]; + m[2][2] = arr[10]; + m[2][3] = arr[11]; + m[3][0] = arr[12]; + m[3][1] = arr[13]; + m[3][2] = arr[14]; + m[3][3] = arr[15]; + } + + inline void set_row(uint32_t row, double x, double y, double z, double w) { + if (row < 4) { + m[row][0] = x; + m[row][1] = y; + m[row][2] = z; + m[row][3] = w; + } + } + + inline void set_scale(double sx, double sy, double sz) { + m[0][0] = sx; + m[0][1] = 0.0; + m[0][2] = 0.0; + m[0][3] = 0.0; + + m[1][0] = 0.0; + m[1][1] = sy; + m[1][2] = 0.0; + m[1][3] = 0.0; + + m[2][0] = 0.0; + m[2][1] = 0.0; + m[2][2] = sz; + m[2][3] = 0.0; + + m[3][0] = 0.0; + m[3][1] = 0.0; + m[3][2] = 0.0; + m[3][3] = 1.0; + } + + static matrix4d identity() { + matrix4d m; + + m.m[0][0] = 1.0; + m.m[0][1] = 0.0; + m.m[0][2] = 0.0; + m.m[0][3] = 0.0; + + m.m[1][0] = 0.0; + m.m[1][1] = 1.0; + m.m[1][2] = 0.0; + m.m[1][3] = 0.0; + + m.m[2][0] = 0.0; + m.m[2][1] = 0.0; + m.m[2][2] = 1.0; + m.m[2][3] = 0.0; + + m.m[3][0] = 0.0; + m.m[3][1] = 0.0; + m.m[3][2] = 0.0; + m.m[3][3] = 1.0; + + return m; + } + + matrix4d &operator=(const matrix4f &rhs); + + double m[4][4]; +}; + +// = matrix4d +struct frame4d { + frame4d() { + m[0][0] = 1.0; + m[0][1] = 0.0; + m[0][2] = 0.0; + m[0][3] = 0.0; + + m[1][0] = 0.0; + m[1][1] = 1.0; + m[1][2] = 0.0; + m[1][3] = 0.0; + + m[2][0] = 0.0; + m[2][1] = 0.0; + m[2][2] = 1.0; + m[2][3] = 0.0; + + m[3][0] = 0.0; + m[3][1] = 0.0; + m[3][2] = 0.0; + m[3][3] = 1.0; + } + double m[4][4]; +}; + +// ret = m x n in row-major(n x m in column-major) +// i.e. You can express TRS transform as +// +// p * S * R * T = p' +// p' = Mult(Mult(S, R), T) +// +// you can express world matrix as +// +// node.world = parent.world * node.local +// = Mult(parent.world, node.local) +template +MTy Mult(const MTy &m, const MTy &n) { + MTy ret; + //memset(ret.m, 0, sizeof(MTy)); + + for (size_t j = 0; j < N; j++) { + for (size_t i = 0; i < N; i++) { + STy value = static_cast(0); + for (size_t k = 0; k < N; k++) { + value += m.m[j][k] * n.m[k][i]; + } + ret.m[j][i] = value; + } + } + + return ret; +} + +#if 0 +// Deprecated. +// TODO: remove column-major functions. +template +MTy MultColumnMajor(const MTy &m, const MTy &n) { + MTy ret; + memset(ret.m, 0, sizeof(MTy)); + + for (size_t j = 0; j < N; j++) { + for (size_t i = 0; i < N; i++) { + STy value = static_cast(0); + for (size_t k = 0; k < N; k++) { + value += m.m[j][k] * n.m[k][i]; + } + ret.m[j][i] = value; + } + } + + return ret; +} +#endif + +// ret = matrix x vector +// Assume matrixN >= vecN +template +VTy MultV(const MTy &m, const VTy &v) { + // MBaseTy must be float or double + // TODO: use std::enable_if? + static_assert(std::is_same::value || std::is_same::value, + "Matrix element type must be `float` or `double`"); + + // Intermediate type. Choose higher precision based on its size. + typedef typename std::conditional= sizeof(VBaseTy), MBaseTy, VBaseTy>::type Ty; + + VTy ret; + + for (size_t j = 0; j < N; j++) { + Ty value = static_cast(0); + for (size_t i = 0; i < N; i++) { + value += static_cast(m.m[i][j]) * static_cast(v[i]); + } + ret[j] = static_cast(value); + } + + return ret; +} + +template +MTy MatAdd(const MTy &m, const MTy &n) { + MTy ret; + memset(ret.m, 0, sizeof(MTy)); + + for (size_t j = 0; j < N; j++) { + for (size_t i = 0; i < N; i++) { + ret.m[j][i] = m.m[j][i] + n.m[j][i]; + } + } + + return ret; +} + +template +MTy MatSub(const MTy &m, const MTy &n) { + MTy ret; + memset(ret.m, 0, sizeof(MTy)); + + for (size_t j = 0; j < N; j++) { + for (size_t i = 0; i < N; i++) { + ret.m[j][i] = m.m[j][i] - n.m[j][i]; + } + } + + return ret; +} + +// TODO: division + +inline matrix2f operator+(const matrix2f &a, const matrix2f &b) { + matrix2f ret = MatAdd(a, b); + return ret; +} + +inline matrix2f operator-(const matrix2f &a, const matrix2f &b) { + matrix2f ret = MatSub(a, b); + return ret; +} + +inline matrix2f operator*(const matrix2f &a, const matrix2f &b) { + matrix2f ret = Mult(a, b); + return ret; +} + +inline matrix3f operator+(const matrix3f &a, const matrix3f &b) { + matrix3f ret = MatAdd(a, b); + return ret; +} + +inline matrix3f operator-(const matrix3f &a, const matrix3f &b) { + matrix3f ret = MatSub(a, b); + return ret; +} + +inline matrix3f operator*(const matrix3f &a, const matrix3f &b) { + matrix3f ret = Mult(a, b); + return ret; +} + +inline matrix4f operator+(const matrix4f &a, const matrix4f &b) { + matrix4f ret = MatAdd(a, b); + return ret; +} + +inline matrix4f operator-(const matrix4f &a, const matrix4f &b) { + matrix4f ret = MatSub(a, b); + return ret; +} + +inline matrix4f operator*(const matrix4f &a, const matrix4f &b) { + matrix4f ret = Mult(a, b); + return ret; +} + +inline matrix2d operator+(const matrix2d &a, const matrix2d &b) { + matrix2d ret = MatAdd(a, b); + return ret; +} + +inline matrix2d operator-(const matrix2d &a, const matrix2d &b) { + matrix2d ret = MatSub(a, b); + return ret; +} + +inline matrix2d operator*(const matrix2d &a, const matrix2d &b) { + matrix2d ret = Mult(a, b); + return ret; +} + +inline matrix3d operator+(const matrix3d &a, const matrix3d &b) { + matrix3d ret = MatAdd(a, b); + return ret; +} + +inline matrix3d operator-(const matrix3d &a, const matrix3d &b) { + matrix3d ret = MatSub(a, b); + return ret; +} + +inline matrix3d operator*(const matrix3d &a, const matrix3d &b) { + matrix3d ret = Mult(a, b); + return ret; +} + +inline matrix4d operator+(const matrix4d &a, const matrix4d &b) { + matrix4d ret = MatAdd(a, b); + return ret; +} + +inline matrix4d operator-(const matrix4d &a, const matrix4d &b) { + matrix4d ret = MatSub(a, b); + return ret; +} + +inline matrix4d operator*(const matrix4d &a, const matrix4d &b) { + matrix4d ret = Mult(a, b); + return ret; +} + +// Quaternion has memory layout of [x, y, z, w] in Crate(Binary) +// and QfQuat class in pxrUSD. +// https://github.com/PixarAnimationStudios/USD/blob/3abc46452b1271df7650e9948fef9f0ce602e3b2/pxr/base/gf/quatf.h#L287 +// NOTE: ASCII uses [w, x, y, z] ordering +struct quath { + half3 imag; + half real; + half operator[](size_t idx) const { return *(&imag[0] + idx); } + half &operator[](size_t idx) { return *(&imag[0] + idx); } +}; + +struct quatf { + float3 imag; + float real; + float operator[](size_t idx) const { return *(&imag[0] + idx); } + float &operator[](size_t idx) { return *(&imag[0] + idx); } +}; + +struct quatd { + double3 imag; + double real; + double operator[](size_t idx) const { return *(&imag[0] + idx); } + double &operator[](size_t idx) { return *(&imag[0] + idx); } +}; + +struct vector3h { + half x, y, z; + + half operator[](size_t idx) const { return *(&x + idx); } + half &operator[](size_t idx) { return *(&x + idx); } +}; + +struct vector3f { + float x, y, z; + + float operator[](size_t idx) const { return *(&x + idx); } + float &operator[](size_t idx) { return *(&x + idx); } +}; + +struct vector3d { + double x, y, z; + + double operator[](size_t idx) const { return *(&x + idx); } + double &operator[](size_t idx) { return *(&x + idx); } +}; + +struct normal3h { + half x, y, z; + + half operator[](size_t idx) const { return *(&x + idx); } + half &operator[](size_t idx) { return *(&x + idx); } +}; + +struct normal3f { + float x, y, z; + + float operator[](size_t idx) const { return *(&x + idx); } + float &operator[](size_t idx) { return *(&x + idx); } +}; + +struct normal3d { + double x, y, z; + + double operator[](size_t idx) const { return *(&x + idx); } + double &operator[](size_t idx) { return *(&x + idx); } +}; + +struct point3h { + half x, y, z; + + half operator[](size_t idx) const { return *(&x + idx); } + half &operator[](size_t idx) { return *(&x + idx); } +}; + +#if 0 // move to value-eval-util.hh + +inline point3h operator+(const float a, const point3h &b) { + return {a + b.x, a + b.y, a + b.z}; +} + +inline point3h operator-(const float a, const point3h &b) { + return {a - b.x, a - b.y, a - b.z}; +} + +inline point3h operator*(const float a, const point3h &b) { + return {a * b.x, a * b.y, a * b.z}; +} + +// TODO: safe div +inline point3h operator/(const float a, const point3h &b) { + return {a / b.x, a / b.y, a / b.z}; +} + +inline point3h operator+(const double a, const point3h &b) { + return {float(a) + b.x, float(a) + b.y, float(a) + b.z}; +} + +inline point3h operator-(const double a, const point3h &b) { + return {float(a) - b.x, float(a) - b.y, float(a) - b.z}; +} + +inline point3h operator*(const double a, const point3h &b) { + return {float(a) * b.x, float(a) * b.y, float(a) * b.z}; +} + +inline point3h operator/(const double a, const point3h &b) { + return {float(a) / b.x, float(a) / b.y, float(a) / b.z}; +} + +inline point3h operator+(const point3h &a, const float b) { + return {a.x + b, a.y + b, a.z + b}; +} + +inline point3h operator-(const point3h &a, const float b) { + return {a.x - b, a.y - b, a.z - b}; +} + +inline point3h operator*(const point3h &a, const float b) { + return {a.x * b, a.y * b, a.z * b}; +} + +inline point3h operator/(const point3h &a, const float b) { + return {a.x / b, a.y / b, a.z / b}; +} + +inline point3h operator+(const point3h &a, const double b) { + return {a.x + float(b), a.y + float(b), a.z + float(b)}; +} + +inline point3h operator-(const point3h &a, const double b) { + return {a.x - float(b), a.y - float(b), a.z - float(b)}; +} + +inline point3h operator*(const point3h &a, const double b) { + return {a.x * float(b), a.y * float(b), a.z * float(b)}; +} + +inline point3h operator/(const point3h &a, const double b) { + return {a.x / float(b), a.y / float(b), a.z / float(b)}; +} + +inline point3h operator+(const point3h &a, const point3h &b) { + return {a.x + b.x, a.y + b.y, a.z + b.z}; +} + +inline point3h operator-(const point3h &a, const point3h &b) { + return {a.x - b.x, a.y - b.y, a.z - b.z}; +} + +inline point3h operator*(const point3h &a, const point3h &b) { + return {a.x * b.x, a.y * b.y, a.z * b.z}; +} + +inline point3h operator/(const point3h &a, const point3h &b) { + return {a.x / b.x, a.y / b.y, a.z / b.z}; +} +#endif + +struct point3f { + float x, y, z; + + float operator[](size_t idx) const { return *(&x + idx); } + float &operator[](size_t idx) { return *(&x + idx); } +}; + +#if 0 +inline point3f operator+(const float a, const point3f &b) { + return {a + b.x, a + b.y, a + b.z}; +} + +inline point3f operator-(const float a, const point3f &b) { + return {a - b.x, a - b.y, a - b.z}; +} + +inline point3f operator*(const float a, const point3f &b) { + return {a * b.x, a * b.y, a * b.z}; +} + +// TODO: safe div +inline point3f operator/(const float a, const point3f &b) { + return {a / b.x, a / b.y, a / b.z}; +} + +inline point3f operator+(const double a, const point3f &b) { + return {float(a) + b.x, float(a) + b.y, float(a) + b.z}; +} + +inline point3f operator-(const double a, const point3f &b) { + return {float(a) - b.x, float(a) - b.y, float(a) - b.z}; +} + +inline point3f operator*(const double a, const point3f &b) { + return {float(a) * b.x, float(a) * b.y, float(a) * b.z}; +} + +inline point3f operator/(const double a, const point3f &b) { + return {float(a) / b.x, float(a) / b.y, float(a) / b.z}; +} + +inline point3f operator+(const point3f &a, const float b) { + return {a.x + b, a.y + b, a.z + b}; +} + +inline point3f operator-(const point3f &a, const float b) { + return {a.x - b, a.y - b, a.z - b}; +} + +inline point3f operator*(const point3f &a, const float b) { + return {a.x * b, a.y * b, a.z * b}; +} + +inline point3f operator/(const point3f &a, const float b) { + return {a.x / b, a.y / b, a.z / b}; +} + +inline point3f operator+(const point3f &a, const double b) { + return {a.x + float(b), a.y + float(b), a.z + float(b)}; +} + +inline point3f operator-(const point3f &a, const double b) { + return {a.x - float(b), a.y - float(b), a.z - float(b)}; +} + +inline point3f operator*(const point3f &a, const double b) { + return {a.x * float(b), a.y * float(b), a.z * float(b)}; +} + +inline point3f operator/(const point3f &a, const double b) { + return {a.x / float(b), a.y / float(b), a.z / float(b)}; +} + +inline point3f operator+(const point3f &a, const point3f &b) { + return {a.x + b.x, a.y + b.y, a.z + b.z}; +} + +inline point3f operator-(const point3f &a, const point3f &b) { + return {a.x - b.x, a.y - b.y, a.z - b.z}; +} + +inline point3f operator*(const point3f &a, const point3f &b) { + return {a.x * b.x, a.y * b.y, a.z * b.z}; +} + +inline point3f operator/(const point3f &a, const point3f &b) { + return {a.x / b.x, a.y / b.y, a.z / b.z}; +} +#endif + +struct point3d { + double x, y, z; + + double operator[](size_t idx) const { return *(&x + idx); } + double &operator[](size_t idx) { return *(&x + idx); } +}; + +#if 0 +inline point3d operator+(const double a, const point3d &b) { + return {a + b.x, a + b.y, a + b.z}; +} + +inline point3d operator-(const double a, const point3d &b) { + return {a - b.x, a - b.y, a - b.z}; +} + +inline point3d operator*(const double a, const point3d &b) { + return {a * b.x, a * b.y, a * b.z}; +} + +// TODO: safe div +inline point3d operator/(const double a, const point3d &b) { + return {a / b.x, a / b.y, a / b.z}; +} + +inline point3d operator+(const float a, const point3d &b) { + return {double(a) + b.x, double(a) + b.y, double(a) + b.z}; +} + +inline point3d operator-(const float a, const point3d &b) { + return {double(a) - b.x, double(a) - b.y, double(a) - b.z}; +} + +inline point3d operator*(const float a, const point3d &b) { + return {double(a) * b.x, double(a) * b.y, double(a) * b.z}; +} + +inline point3d operator/(const float a, const point3d &b) { + return {double(a) / b.x, double(a) / b.y, double(a) / b.z}; +} + +inline point3d operator+(const point3d &a, const double b) { + return {a.x + b, a.y + b, a.z + b}; +} + +inline point3d operator-(const point3d &a, const double b) { + return {a.x - b, a.y - b, a.z - b}; +} + +inline point3d operator*(const point3d &a, const double b) { + return {a.x * b, a.y * b, a.z * b}; +} + +inline point3d operator/(const point3d &a, const double b) { + return {a.x / b, a.y / b, a.z / b}; +} + +inline point3d operator+(const point3d &a, const float b) { + return {a.x + double(b), a.y + double(b), a.z + double(b)}; +} + +inline point3d operator-(const point3d &a, const float b) { + return {a.x - double(b), a.y - double(b), a.z - double(b)}; +} + +inline point3d operator*(const point3d &a, const float b) { + return {a.x * double(b), a.y * double(b), a.z * double(b)}; +} + +inline point3d operator/(const point3d &a, const float b) { + return {a.x / double(b), a.y / double(b), a.z / double(b)}; +} + +inline point3d operator+(const point3d &a, const point3d &b) { + return {a.x + b.x, a.y + b.y, a.z + b.z}; +} + +inline point3d operator-(const point3d &a, const point3d &b) { + return {a.x - b.x, a.y - b.y, a.z - b.z}; +} + +inline point3d operator*(const point3d &a, const point3d &b) { + return {a.x * b.x, a.y * b.y, a.z * b.z}; +} + +inline point3d operator/(const point3d &a, const point3d &b) { + return {a.x / b.x, a.y / b.y, a.z / b.z}; +} +#endif + +struct color3h { + half r, g, b; + + half operator[](size_t idx) const { return *(&r + idx); } + half &operator[](size_t idx) { return *(&r + idx); } +}; + +struct color3f { + float r, g, b; + + float operator[](size_t idx) const { return *(&r + idx); } + float &operator[](size_t idx) { return *(&r + idx); } +}; + +struct color4h { + half r, g, b, a; + + half operator[](size_t idx) const { return *(&r + idx); } + half &operator[](size_t idx) { return *(&r + idx); } +}; + +struct color4f { + float r, g, b, a; + + float operator[](size_t idx) const { return *(&r + idx); } + float &operator[](size_t idx) { return *(&r + idx); } +}; + +struct color3d { + double r, g, b; + + double operator[](size_t idx) const { return *(&r + idx); } + double &operator[](size_t idx) { return *(&r + idx); } +}; + +struct color4d { + double r, g, b, a; + + double operator[](size_t idx) const { return *(&r + idx); } + double &operator[](size_t idx) { return *(&r + idx); } +}; + +struct texcoord2h { + half s, t; + half operator[](size_t idx) const { return *(&s + idx); } + half &operator[](size_t idx) { return *(&s + idx); } +}; + +struct texcoord2f { + float s, t; + float operator[](size_t idx) const { return *(&s + idx); } + float &operator[](size_t idx) { return *(&s + idx); } +}; + +struct texcoord2d { + double s, t; + double operator[](size_t idx) const { return *(&s + idx); } + double &operator[](size_t idx) { return *(&s + idx); } +}; + +struct texcoord3h { + half s, t, r; + half operator[](size_t idx) const { return *(&s + idx); } + half &operator[](size_t idx) { return *(&s + idx); } +}; + +struct texcoord3f { + float s, t, r; + float operator[](size_t idx) const { return *(&s + idx); } + float &operator[](size_t idx) { return *(&s + idx); } +}; + +struct texcoord3d { + double s, t, r; + double operator[](size_t idx) const { return *(&s + idx); } + double &operator[](size_t idx) { return *(&s + idx); } +}; + + + +// Attribute value Block(`None`) +struct ValueBlock {}; + +using double2 = std::array; +using double3 = std::array; +using double4 = std::array; + +// struct any_value; +// using dict = std::map; +using dict = std::map; + +template +struct TypeTraits; + +// import DEFINE_TYPE_TRAIT and DEFINE_ROLE_TYPE_TRAIT +#include "define-type-trait.inc" + +// `void` hash no sizeof(void), so define it manually. +template <> +struct TypeTraits { + using value_type = void; + using value_underlying_type = void; + static constexpr uint32_t ndim() { return 0; } /* array dim */ + static constexpr uint32_t size = 0; /* zero for void */ + static constexpr uint32_t ncomp() { return 0; } + static constexpr uint32_t type_id() { return TYPE_ID_VOID; } + static constexpr uint32_t get_type_id() { return TYPE_ID_VOID; } + static constexpr uint32_t underlying_type_id() { return TYPE_ID_VOID; } + static std::string type_name() { return "void"; } + static std::string underlying_type_name() { return "void"; } + static bool is_role_type() { return false; } + static bool is_array() { return false; } +}; + +DEFINE_TYPE_TRAIT(std::nullptr_t, "null", TYPE_ID_NULL, 1); +// DEFINE_TYPE_TRAIT(void, "void", TYPE_ID_VOID, 1); +DEFINE_TYPE_TRAIT(ValueBlock, "None", TYPE_ID_VALUEBLOCK, 1); + +DEFINE_TYPE_TRAIT(bool, kBool, TYPE_ID_BOOL, 1); +DEFINE_TYPE_TRAIT(uint8_t, kUChar, TYPE_ID_UCHAR, 1); +DEFINE_TYPE_TRAIT(half, kHalf, TYPE_ID_HALF, 1); + +DEFINE_TYPE_TRAIT(int16_t, kShort, TYPE_ID_SHORT, 1); +DEFINE_TYPE_TRAIT(uint16_t, kUShort, TYPE_ID_USHORT, 1); + +DEFINE_TYPE_TRAIT(int32_t, kInt, TYPE_ID_INT32, 1); +DEFINE_TYPE_TRAIT(uint32_t, kUInt, TYPE_ID_UINT32, 1); + +DEFINE_TYPE_TRAIT(int64_t, kInt64, TYPE_ID_INT64, 1); +DEFINE_TYPE_TRAIT(uint64_t, kUInt64, TYPE_ID_UINT64, 1); + +DEFINE_TYPE_TRAIT(char, kChar, TYPE_ID_CHAR, 1); +DEFINE_TYPE_TRAIT(char2, kChar2, TYPE_ID_CHAR2, 2); +DEFINE_TYPE_TRAIT(char3, kChar3, TYPE_ID_CHAR3, 3); +DEFINE_TYPE_TRAIT(char4, kChar4, TYPE_ID_CHAR4, 4); + +DEFINE_TYPE_TRAIT(uchar2, kUChar2, TYPE_ID_UCHAR2, 2); +DEFINE_TYPE_TRAIT(uchar3, kUChar3, TYPE_ID_UCHAR3, 3); +DEFINE_TYPE_TRAIT(uchar4, kUChar4, TYPE_ID_UCHAR4, 4); + +DEFINE_TYPE_TRAIT(short2, kShort2, TYPE_ID_SHORT2, 2); +DEFINE_TYPE_TRAIT(short3, kShort3, TYPE_ID_SHORT3, 3); +DEFINE_TYPE_TRAIT(short4, kShort4, TYPE_ID_SHORT4, 4); + +DEFINE_TYPE_TRAIT(ushort2, kUShort2, TYPE_ID_USHORT2, 2); +DEFINE_TYPE_TRAIT(ushort3, kUShort3, TYPE_ID_USHORT3, 3); +DEFINE_TYPE_TRAIT(ushort4, kUShort4, TYPE_ID_USHORT4, 4); + +DEFINE_TYPE_TRAIT(int2, kInt2, TYPE_ID_INT2, 2); +DEFINE_TYPE_TRAIT(int3, kInt3, TYPE_ID_INT3, 3); +DEFINE_TYPE_TRAIT(int4, kInt4, TYPE_ID_INT4, 4); + +DEFINE_TYPE_TRAIT(uint2, kUInt2, TYPE_ID_UINT2, 2); +DEFINE_TYPE_TRAIT(uint3, kUInt3, TYPE_ID_UINT3, 3); +DEFINE_TYPE_TRAIT(uint4, kUInt4, TYPE_ID_UINT4, 4); + +DEFINE_TYPE_TRAIT(half2, kHalf2, TYPE_ID_HALF2, 2); +DEFINE_TYPE_TRAIT(half3, kHalf3, TYPE_ID_HALF3, 3); +DEFINE_TYPE_TRAIT(half4, kHalf4, TYPE_ID_HALF4, 4); + +DEFINE_TYPE_TRAIT(float, kFloat, TYPE_ID_FLOAT, 1); +DEFINE_TYPE_TRAIT(float2, kFloat2, TYPE_ID_FLOAT2, 2); +DEFINE_TYPE_TRAIT(float3, kFloat3, TYPE_ID_FLOAT3, 3); +DEFINE_TYPE_TRAIT(float4, kFloat4, TYPE_ID_FLOAT4, 4); + +DEFINE_TYPE_TRAIT(double, kDouble, TYPE_ID_DOUBLE, 1); +DEFINE_TYPE_TRAIT(double2, kDouble2, TYPE_ID_DOUBLE2, 2); +DEFINE_TYPE_TRAIT(double3, kDouble3, TYPE_ID_DOUBLE3, 3); +DEFINE_TYPE_TRAIT(double4, kDouble4, TYPE_ID_DOUBLE4, 4); + +DEFINE_TYPE_TRAIT(quath, kQuath, TYPE_ID_QUATH, 1); +DEFINE_TYPE_TRAIT(quatf, kQuatf, TYPE_ID_QUATF, 1); +DEFINE_TYPE_TRAIT(quatd, kQuatd, TYPE_ID_QUATD, 1); + +DEFINE_TYPE_TRAIT(matrix2f, kMatrix2f, TYPE_ID_MATRIX2F, 1); +DEFINE_TYPE_TRAIT(matrix3f, kMatrix3f, TYPE_ID_MATRIX3F, 1); +DEFINE_TYPE_TRAIT(matrix4f, kMatrix4f, TYPE_ID_MATRIX4F, 1); + +DEFINE_TYPE_TRAIT(matrix2d, kMatrix2d, TYPE_ID_MATRIX2D, 1); +DEFINE_TYPE_TRAIT(matrix3d, kMatrix3d, TYPE_ID_MATRIX3D, 1); +DEFINE_TYPE_TRAIT(matrix4d, kMatrix4d, TYPE_ID_MATRIX4D, 1); + +DEFINE_TYPE_TRAIT(timecode, kTimeCode, TYPE_ID_TIMECODE, 1); + +// +// Role types +// +DEFINE_ROLE_TYPE_TRAIT(vector3h, kVector3h, TYPE_ID_VECTOR3H, half3); +DEFINE_ROLE_TYPE_TRAIT(vector3f, kVector3f, TYPE_ID_VECTOR3F, float3); +DEFINE_ROLE_TYPE_TRAIT(vector3d, kVector3d, TYPE_ID_VECTOR3D, double3); + +DEFINE_ROLE_TYPE_TRAIT(normal3h, kNormal3h, TYPE_ID_NORMAL3H, half3); +DEFINE_ROLE_TYPE_TRAIT(normal3f, kNormal3f, TYPE_ID_NORMAL3F, float3); +DEFINE_ROLE_TYPE_TRAIT(normal3d, kNormal3d, TYPE_ID_NORMAL3D, double3); + +DEFINE_ROLE_TYPE_TRAIT(point3h, kPoint3h, TYPE_ID_POINT3H, half3); +DEFINE_ROLE_TYPE_TRAIT(point3f, kPoint3f, TYPE_ID_POINT3F, float3); +DEFINE_ROLE_TYPE_TRAIT(point3d, kPoint3d, TYPE_ID_POINT3D, double3); + +DEFINE_ROLE_TYPE_TRAIT(frame4d, kFrame4d, TYPE_ID_FRAME4D, matrix4d); + +DEFINE_ROLE_TYPE_TRAIT(color3h, kColor3h, TYPE_ID_COLOR3H, half3); +DEFINE_ROLE_TYPE_TRAIT(color4h, kColor4h, TYPE_ID_COLOR4H, half4); +DEFINE_ROLE_TYPE_TRAIT(color3f, kColor3f, TYPE_ID_COLOR3F, float3); +DEFINE_ROLE_TYPE_TRAIT(color4f, kColor4f, TYPE_ID_COLOR4F, float4); +DEFINE_ROLE_TYPE_TRAIT(color3d, kColor3d, TYPE_ID_COLOR3D, double3); +DEFINE_ROLE_TYPE_TRAIT(color4d, kColor4d, TYPE_ID_COLOR4D, double4); + +DEFINE_ROLE_TYPE_TRAIT(texcoord2h, kTexCoord2h, TYPE_ID_TEXCOORD2H, half2); +DEFINE_ROLE_TYPE_TRAIT(texcoord2f, kTexCoord2f, TYPE_ID_TEXCOORD2F, float2); +DEFINE_ROLE_TYPE_TRAIT(texcoord2d, kTexCoord2d, TYPE_ID_TEXCOORD2D, double2); + +DEFINE_ROLE_TYPE_TRAIT(texcoord3h, kTexCoord3h, TYPE_ID_TEXCOORD3H, half3); +DEFINE_ROLE_TYPE_TRAIT(texcoord3f, kTexCoord3f, TYPE_ID_TEXCOORD3F, float3); +DEFINE_ROLE_TYPE_TRAIT(texcoord3d, kTexCoord3d, TYPE_ID_TEXCOORD3D, double3); + +// +// +// + +DEFINE_TYPE_TRAIT(token, kToken, TYPE_ID_TOKEN, 1); +DEFINE_TYPE_TRAIT(std::string, kString, TYPE_ID_STRING, 1); +DEFINE_TYPE_TRAIT(StringData, kString, TYPE_ID_STRING_DATA, 1); +DEFINE_TYPE_TRAIT(dict, kDictionary, TYPE_ID_DICT, 1); + +DEFINE_TYPE_TRAIT(AssetPath, kAssetPath, TYPE_ID_ASSET_PATH, 1); + +// +// Other types(e.g. TYPE_ID_REFERENCE) are defined in corresponding header +// files(e.g. `prim-types.hh`, `crate-format.hh`(Data types used in Crate data)) +// + +#undef DEFINE_TYPE_TRAIT +#undef DEFINE_ROLE_TYPE_TRAIT + +// 1D Array +template +struct TypeTraits> { + using value_type = std::vector; + static constexpr uint32_t ndim() { return 1; } /* array dim */ + static constexpr uint32_t ncomp() { return TypeTraits::ncomp(); } + // Return the size of base type + static constexpr size_t size() { return TypeTraits::size(); } + static constexpr uint32_t type_id() { return + TypeTraits::type_id() | TYPE_ID_1D_ARRAY_BIT; } + static constexpr uint32_t get_type_id() { + return TypeTraits::type_id() | TYPE_ID_1D_ARRAY_BIT; } + static constexpr uint32_t underlying_type_id() { + return TypeTraits::underlying_type_id() | TYPE_ID_1D_ARRAY_BIT; } + static std::string type_name() { return TypeTraits::type_name() + "[]"; } + static std::string underlying_type_name() { + return TypeTraits::underlying_type_name() + "[]"; + } + static constexpr bool is_role_type() { return TypeTraits::is_role_type(); } + static constexpr bool is_array() { return true; } +}; + +#if 0 // Current pxrUSD does not support 2D array +// 2D Array +// TODO(syoyo): support 3D array? +template +struct TypeTraits>> { + using value_type = std::vector>; + static constexpr uint32_t ndim = 2; /* array dim */ + static constexpr uint32_t ncomp = TypeTraits::ncomp; + static constexpr uint32_t type_id = + TypeTraits::type_id | TYPE_ID_2D_ARRAY_BIT; + static constexpr uint32_t underlying_type_id = + TypeTraits::underlying_type_id | TYPE_ID_2D_ARRAY_BIT; + static std::string type_name() { return TypeTraits::type_name() + "[][]"; } + static std::string underlying_type_name() { + return TypeTraits::underlying_type_name() + "[][]"; + } +}; +#endif + +// Lookup TypeTraits::type_name from type_id +// Return nullopt when the input is invalid type id. +nonstd::optional TryGetTypeName(uint32_t tyid); + +// Return error string when the input is invalid type id +std::string GetTypeName(uint32_t tyid); + +// Lookup TypeTraits::type_id from string +// Return nullopt when the input is invalid type name. +nonstd::optional TryGetTypeId(const std::string &tyname); + +// Return TYPE_ID_INVALID when the input is invalid type name +uint32_t GetTypeId(const std::string &tyname); + +// For Role type. +// Get underlying type name(e.g. return type "float4" for role type "color4f"), +// or return nullopt/invalid string for invalid input type id. For non-Role +// type, the behavior is same with TryGetTypeName/GetTypeName(i.e, return +// "float4" for type `float4`) +nonstd::optional TryGetUnderlyingTypeName(uint32_t tyid); +std::string GetUnderlyingTypeName(uint32_t tyid); + +// Get underlying type id(e.g. return type "float4" for role type "color4f"), or +// return nullopt/TYPE_ID_INVALID for invalid input type name For non-Role type, +// the behavior is same with TryGetTypeId/GetTypeId(i.e, return `float4` for +// name "float4") +nonstd::optional TryGetUnderlyingTypeId(const std::string &tyname); +uint32_t GetUnderlyingTypeId(const std::string &tyname); + +// TODO: uint32_t GetUnderlyingTypeId(const uint32_t tyid) + +/// @brief Check if given typeName string is a role-type(e.g. "vector3f") +/// @param[in] tyname typeName string +/// @return true if a type is role-type. +bool IsRoleType(const std::string &tyname); + +/// @brief Check if given type_id is a role-type(e.g. "vector3f") +/// @param[in] tyid type id(value::TYPE_ID_***) +/// @return true if a type is role-type. +bool IsRoleType(const uint32_t tyid); + +} // namespace value +} // namespace tinyusdz + +#include "tiny-any.inc" + +namespace tinyusdz { +namespace value { + +/// +/// Generic Value class using any +/// TODO: Type-check when casting with underlying_type(Need to modify linb::any +/// class) +/// +class Value { + public: + Value() = default; + + template + Value(const T &v) : v_(v) {} + + // template + // Value(T &&v) : v_(v) {} + + const std::string type_name() const { return v_.type_name(); } + const std::string underlying_type_name() const { + return v_.underlying_type_name(); + } + + uint32_t type_id() const { return v_.type_id(); } + uint32_t underlying_type_id() const { return v_.underlying_type_id(); } + + // + // Cast value to given type. + // + // when `strict_cast` is false(default behavior), it supports casting type among role type and underlying type. + // (e.g. "float3" -> "color3f", "color3f" -> "vector3f", "normal3f[]" -> "float3[]") + // + // Return nullptr when type conversion failed. + template + const T *as(bool strict_cast = false) const { + if (TypeTraits::type_id() == v_.type_id()) { + return linb::any_cast(&v_); + } else if (!strict_cast) { + // NOTE: linb::any_cast does type_id check, so use linb::cast(~= reinterpret_cast) here + if (TypeTraits::is_array() && (v_.type_id() & value::TYPE_ID_1D_ARRAY_BIT)) { // both are array type + if ((TypeTraits::underlying_type_id() & (~value::TYPE_ID_1D_ARRAY_BIT)) == (v_.underlying_type_id() & (~value::TYPE_ID_1D_ARRAY_BIT))) { + return linb::cast(&v_); + } + } else if (!TypeTraits::is_array() && !(v_.type_id() & value::TYPE_ID_1D_ARRAY_BIT)) { // both are scalar type. + if (TypeTraits::underlying_type_id() == v_.underlying_type_id()) { + return linb::cast(&v_); + } + } + } + + return nullptr; + } + + // Non const version of `as`. + // + // Return nullptr when type conversion failed. + template + T *as(bool strict_cast = false) { + if (TypeTraits::type_id() == v_.type_id()) { + return linb::any_cast(&v_); + } else if (!strict_cast) { + if (TypeTraits::is_array() && (v_.type_id() & value::TYPE_ID_1D_ARRAY_BIT)) { // both are array type + if ((TypeTraits::underlying_type_id() & (~value::TYPE_ID_1D_ARRAY_BIT)) == (v_.underlying_type_id() & (~value::TYPE_ID_1D_ARRAY_BIT))) { + return linb::cast(&v_); + } + } else if (!TypeTraits::is_array() && !(v_.type_id() & value::TYPE_ID_1D_ARRAY_BIT)) { // both are scalar type. + if (TypeTraits::underlying_type_id() == v_.underlying_type_id()) { + return linb::cast(&v_); + } + } + } + + return nullptr; + } + + +#if 0 + // Useful function to retrieve concrete value with type T. + // Undefined behavior(usually will triger segmentation fault) when + // type-mismatch. (We don't throw exception) + template + const T value() const { + //return (*reinterpret_cast(v_.value())); + return linb::any_cast(v_); + } +#endif + + // Type-safe way to get concrete value. + template + nonstd::optional get_value(bool strict_cast = false) const { + if (TypeTraits::type_id() == v_.type_id()) { + const T *pv = linb::any_cast(&v_); + if (!pv) { + // ??? + return nonstd::nullopt; + } + + return std::move(*pv); + } else if (!strict_cast) { + + if (TypeTraits::is_array() && (v_.type_id() & value::TYPE_ID_1D_ARRAY_BIT)) { // both are array type + if ((TypeTraits::underlying_type_id() & (~value::TYPE_ID_1D_ARRAY_BIT)) == (v_.underlying_type_id() & (~value::TYPE_ID_1D_ARRAY_BIT))) { + return std::move(*linb::cast(&v_)); + } + } else if (!TypeTraits::is_array() && !(v_.type_id() & value::TYPE_ID_1D_ARRAY_BIT)) { // both are scalar type. + if (TypeTraits::underlying_type_id() == v_.underlying_type_id()) { + return std::move(*linb::cast(&v_)); + } + } + } + return nonstd::nullopt; + } + + + template + Value &operator=(const T &v) { + v_ = v; + return (*this); + } + + const linb::any &get_raw() const { return v_; } + + bool is_array() const { return (v_.type_id() & value::TYPE_ID_1D_ARRAY_BIT); } + + // return 0 for non array type. + // This method is primaliry for Primvar types(`float[]`, `color3f[]`, ...) + // It does not report non-Primvar types(e.g. `Reference`, `Xform`, `GeomMesh`, + // ...) + size_t array_size() const; + + bool is_empty() const { return v_.type_id() == value::TYPE_ID_NULL; } + + private: + // any_value v_; + linb::any v_{nullptr}; +}; + +// TimeSample interpolation type. +// +// Held = something like numpy.digitize(right=False) +// https://numpy.org/doc/stable/reference/generated/numpy.digitize.html +// +// Returns `values[i-1]` for `times[i-1] <= t < times[i]` +// +// Linear = linear interpolation +// +// example: +// { 0 : 0.0 +// 10 : 1.0 +// } +// +// - Held +// - time 5 = returns 0.0 +// - time 9.99 = returns 0.0 +// - time 10 = returns 1.0 +// - Linear +// - time 5 = returns 0.5 +// - time 9.99 = nearly 1.0 +// - time 10 = 1.0 +// +enum class TimeSampleInterpolationType { + Held, // something like nearest-neighbor. + Linear, +}; + +// +// Supported type for `Linear` interpolation +// +// half, float, double, TimeCode(double) +// matrix2d, matrix3d, matrix4d, +// float2h, float3h, float4h +// float2f, float3f, float4f +// float2d, float3d, float4d +// quath, quatf, quatd +// (use slerp for quaternion type) + +bool IsLerpSupportedType(uint32_t tyid); + +template +struct LerpTraits +{ + static constexpr bool supported() { + return false; + } +}; + +#define DEFINE_LERP_TRAIT(ty) \ +template <> \ +struct LerpTraits { \ + static constexpr bool supported() { \ + return true; \ + } \ +}; + +DEFINE_LERP_TRAIT(value::half) +DEFINE_LERP_TRAIT(value::half2) +DEFINE_LERP_TRAIT(value::half3) +DEFINE_LERP_TRAIT(value::half4) +DEFINE_LERP_TRAIT(float) +DEFINE_LERP_TRAIT(value::float2) +DEFINE_LERP_TRAIT(value::float3) +DEFINE_LERP_TRAIT(value::float4) +DEFINE_LERP_TRAIT(double) +DEFINE_LERP_TRAIT(value::double2) +DEFINE_LERP_TRAIT(value::double3) +DEFINE_LERP_TRAIT(value::double4) +DEFINE_LERP_TRAIT(value::quath) +DEFINE_LERP_TRAIT(value::quatf) +DEFINE_LERP_TRAIT(value::quatd) +DEFINE_LERP_TRAIT(value::matrix2f) +DEFINE_LERP_TRAIT(value::matrix3f) +DEFINE_LERP_TRAIT(value::matrix4f) +DEFINE_LERP_TRAIT(value::matrix2d) +DEFINE_LERP_TRAIT(value::matrix3d) +DEFINE_LERP_TRAIT(value::matrix4d) +DEFINE_LERP_TRAIT(value::timecode) +DEFINE_LERP_TRAIT(value::normal3h) +DEFINE_LERP_TRAIT(value::normal3f) +DEFINE_LERP_TRAIT(value::normal3d) +DEFINE_LERP_TRAIT(value::vector3h) +DEFINE_LERP_TRAIT(value::vector3f) +DEFINE_LERP_TRAIT(value::vector3d) +DEFINE_LERP_TRAIT(value::point3h) +DEFINE_LERP_TRAIT(value::point3f) +DEFINE_LERP_TRAIT(value::point3d) +DEFINE_LERP_TRAIT(value::color3h) +DEFINE_LERP_TRAIT(value::color3f) +DEFINE_LERP_TRAIT(value::color3d) +DEFINE_LERP_TRAIT(value::color4h) +DEFINE_LERP_TRAIT(value::color4f) +DEFINE_LERP_TRAIT(value::color4d) +DEFINE_LERP_TRAIT(value::texcoord2h) +DEFINE_LERP_TRAIT(value::texcoord2f) +DEFINE_LERP_TRAIT(value::texcoord2d) +DEFINE_LERP_TRAIT(value::texcoord3h) +DEFINE_LERP_TRAIT(value::texcoord3f) +DEFINE_LERP_TRAIT(value::texcoord3d) +DEFINE_LERP_TRAIT(value::frame4d) + +#undef DEFINE_LERP_TRAIT + +/// +/// @param[in] dt interpolator [0.0, 1.0) +/// +bool Lerp(const value::Value &a, const value::Value &b, double dt, + value::Value *dst); + + + +// Handy, but may not be efficient for large time samples(e.g. 1M samples or +// more) +// +// For the runtime speed, with "-O2 -g" optimization, adding 10M `double` +// samples to linb::any takes roughly 1.8 ms on Threadripper 1950X, whereas +// simple vector push_back takes 390 us(roughly x4 times faster). (Build +// benchmarks to see the numbers on your CPU) +// +// We assume having large time samples is rare situlation, and above benchmark +// speed is acceptable in general usecases. +// +// `None`(ValueBlock) is represented by setting `Sample::blocked` true. +// +struct TimeSamples { + struct Sample { + double t; + value::Value value; + bool blocked{false}; + }; + + bool empty() const { return _samples.empty(); } + + size_t size() const { return _samples.size(); } + + void clear() { + _samples.clear(); + _dirty = true; + } + + void update() const { + std::sort(_samples.begin(), _samples.end(), + [](const Sample &a, const Sample &b) { return a.t < b.t; }); + + _dirty = false; + } + + nonstd::optional get_time(size_t idx) const { + if (idx >= _samples.size()) { + return nonstd::nullopt; + } + + if (_dirty) { + update(); + } + + return _samples[idx].t; + } + + nonstd::optional get_value(size_t idx) const { + if (idx >= _samples.size()) { + return nonstd::nullopt; + } + + if (_dirty) { + update(); + } + + return _samples[idx].value; + } + + uint32_t type_id() const { + if (_samples.size()) { + if (_dirty) { + update(); + } + return _samples[0].value.type_id(); + } else { + return value::TypeId::TYPE_ID_INVALID; + } + } + + std::string type_name() const { + if (_samples.size()) { + if (_dirty) { + update(); + } + return _samples[0].value.type_name(); + } else { + return std::string(); + } + } + + void add_sample(const Sample &s) { + _samples.push_back(s); + _dirty = true; + } + + void add_sample(double t, const value::Value &v) { + Sample s; + s.t = t; + s.value = v; + s.blocked = false; + _samples.push_back(s); + _dirty = true; + } + + // We still need "dummy" value for type_name() and type_id() + void add_blocked_sample(double t, const value::Value &v) { + Sample s; + s.t = t; + s.value = v; + s.blocked = true; + + _samples.emplace_back(s); + _dirty = true; + } + + const std::vector &get_samples() const { + if (_dirty) { + update(); + } + return _samples; + } + + std::vector &samples() { + if (_dirty) { + update(); + } + return _samples; + } + +#if 1 // TODO: Write implementation in .cc + + // Get value at specified time. + // For non-interpolatable types(includes enums and unknown types) + // + // Return `Held` value even when TimeSampleInterpolationType is + // Linear. Returns nullopt when specified time is out-of-range. + template::supported(), std::nullptr_t> = nullptr> + bool get(T *dst, double t = value::TimeCode::Default(), + value::TimeSampleInterpolationType interp = + value::TimeSampleInterpolationType::Held) const { + + (void)interp; + + if (!dst) { + return false; + } + + if (empty()) { + return false; + } + + if (_dirty) { + update(); + } + + if (value::TimeCode(t).is_default()) { + // TODO: Handle bloked + if (const auto pv = _samples[0].value.as()) { + (*dst) = *pv; + return true; + } + return false; + } else { + + if (_samples.size() == 1) { + if (const auto pv = _samples[0].value.as()) { + (*dst) = *pv; + return true; + } + return false; + } + + auto it = std::lower_bound( + _samples.begin(), _samples.end(), t, + [](const Sample &a, double tval) { return a.t < tval; }); + + if (it == _samples.end()) { + // ??? + return false; + } + + const value::Value &v = it->value; + + if (const T *pv = v.as()) { + (*dst) = *pv; + return true; + } + return false; + } + } + + // Get value at specified time. + // Return linearly interpolated value when TimeSampleInterpolationType is + // Linear. Returns false when samples is empty or some internal error. + template::supported(), std::nullptr_t> = nullptr> + bool get(T *dst, double t = value::TimeCode::Default(), + TimeSampleInterpolationType interp = + TimeSampleInterpolationType::Held) const { + if (!dst) { + return false; + } + + if (empty()) { + return false; + } + + if (_dirty) { + update(); + } + + if (value::TimeCode(t).is_default()) { + // FIXME: Use the first item for now. + // TODO: Handle bloked + if (const auto pv = _samples[0].value.as()) { + (*dst) = *pv; + return true; + } + return false; + } else { + + if (_samples.size() == 1) { + if (const auto pv = _samples[0].value.as()) { + (*dst) = *pv; + return true; + } + return true; + } + + auto it = std::lower_bound( + _samples.begin(), _samples.end(), t, + [](const Sample &a, double tval) { return a.t < tval; }); + + if (interp == TimeSampleInterpolationType::Linear) { + + // MS STL does not allow seek vector iterator before begin + // Issue #110 + const auto it_minus_1 = (it == _samples.begin()) ? _samples.begin() : (it - 1); + + size_t idx0 = size_t(std::max( + int64_t(0), + std::min(int64_t(_samples.size() - 1), + int64_t(std::distance(_samples.begin(), it_minus_1))))); + size_t idx1 = + size_t(std::max(int64_t(0), std::min(int64_t(_samples.size() - 1), + int64_t(idx0) + 1))); + + double tl = _samples[idx0].t; + double tu = _samples[idx1].t; + + double dt = (t - tl); + if (std::fabs(tu - tl) < std::numeric_limits::epsilon()) { + // slope is zero. + dt = 0.0; + } else { + dt /= (tu - tl); + } + + // Just in case. + dt = std::max(0.0, std::min(1.0, dt)); + + const value::Value &p0 = _samples[idx0].value; + const value::Value &p1 = _samples[idx1].value; + + value::Value p; + if (!Lerp(p0, p1, dt, &p)) { + return false; + } + + if (const auto pv = p.as()) { + (*dst) = *pv; + return true; + } + return false; + } else { + if (it == _samples.end()) { + // ??? + return false; + } + + const value::Value &v = it->value; + if (const T *pv = v.as()) { + (*dst) = *pv; + return true; + } + + return false; + } + } + + return false; + } +#endif + + private: + mutable std::vector _samples; + mutable bool _dirty{false}; +}; + + + +/// +/// Try to cast the value with src type to dest type as much as possible. +/// When src type is scalar type and dest type is vector, the value of src type is scattered to the value of dest type. +/// +/// No lexical cast feature involved. +/// TODO: overflow check +/// +/// Considers role type. +/// example: +/// - float3 -> normal3 : OK +/// - float3 -> normal3 : OK +/// - normal3 -> float : OK +/// - normal3 -> int : OK(use normal3[0]) +/// - float2 -> normal3 : OK +/// - float3 -> texcoord2 : OK(use float3[0] and float3[1]) +/// - string -> float : NG +/// - float -> string : NG +/// - float -> string : NG +/// + +bool FlexibleValueConvert(const value::Value &src, value::Value &dst); + +template +bool FlexibleTypeCast(const SrcT &src, DestT &dst) { + value::Value srcv(src); + value::Value dstv(dst); + + return FlexibleValueConvert(srcv, dstv); +} + +/// +/// Cast input value's type to Role type +/// Return true: cast success. +/// +bool RoleTypeCast(const uint32_t roleTyId, value::Value &inout); + +/// +/// Upcast value to specified type(e.g. `half` -> `float`) +/// Return true: Upcast success. +/// +bool UpcastType(const std::string &toType, value::Value &inout); + +#if 0 +// simple linear interpolator +template +struct LinearInterpolator { + static T interpolate(const T *values, const size_t n, const double _t) { + if (n == 0) { + return static_cast(0); + } else if (n == 1) { + return values[0]; + } + + // [0.0, 1.0] + double t = std::fmin(0.0, std::fmax(_t, 1.0)); + + size_t idx0 = std::max(n - 1, size_t(t * double(n))); + size_t idx1 = std::max(n - 1, idx0 + 1); + + return (1.0 - t) * values[idx0] + t * values[idx1]; + } +}; + +// Explicitly typed version of `TimeSamples` +// +// `None` value and `deleted` items are omitted in this data struct. +// e.g. +// +// double radius.timeSamples = { 0: 1.0, 1: None, 2: 3.0 } +// +// in .usd(or `TimeSamples` class), are stored as +// +// radius = { 0: 1.0, 2: 3.0 } +// +template +struct AnimatableValue { + std::vector times; // Assume sorted + std::vector values; + + bool is_scalar() const { return (times.size() == 0) && (values.size() == 1); } + + bool is_timesample() const { + return (times.size() > 0) && (times.size() == values.size()); + } + + template + T Get(double time = 0.0) { + std::vector::iterator it = + std::lower_bound(times.begin(), times.end(), time); + + size_t idx0, idx1; + if (it != times.end()) { + idx0 = std::distance(times.begin(), it); + idx1 = std::min(idx0 + 1, times.size() - 1); + } else { + idx0 = idx1 = times.size() - 1; + } + double slope = times[idx1] - times[idx0]; + if (slope < std::numeric_limits::epsilon()) { + slope = 1.0; + } + + const double t = (times[idx1] - time) / slope; + + T val = Interpolator::interpolate(values.data(), values.size(), t); + return val; + } +}; +#endif + +#if 0 // TODO: Remove? since not used so frequently at the moment. +// +// typecast from type_id +// It does not throw exception. +// +template +struct typecast {}; + +#define TYPECAST_BASETYPE(__tid, __ty) \ + template <> \ + struct typecast<__tid> { \ + static __ty to(const any_value &v) { \ + return *reinterpret_cast(v.value()); \ + } \ + } + +TYPECAST_BASETYPE(TYPE_ID_BOOL, bool); +TYPECAST_BASETYPE(TYPE_ID_UCHAR, uint8_t); +TYPECAST_BASETYPE(TYPE_ID_HALF, half); +TYPECAST_BASETYPE(TYPE_ID_HALF2, half2); +TYPECAST_BASETYPE(TYPE_ID_HALF3, half3); +TYPECAST_BASETYPE(TYPE_ID_HALF4, half4); + +TYPECAST_BASETYPE(TYPE_ID_UINT32, uint32_t); +TYPECAST_BASETYPE(TYPE_ID_FLOAT, float); +TYPECAST_BASETYPE(TYPE_ID_DOUBLE, double); + +TYPECAST_BASETYPE(TYPE_ID_FLOAT | TYPE_ID_1D_ARRAY_BIT, std::vector); + +// TODO(syoyo): Implement more types... + +#undef TYPECAST_BASETYPE +#endif + +#if 0 +struct AttribMap { + std::map attribs; +}; +#endif + +} // namespace value + +} // namespace tinyusdz + +namespace tinyusdz { +namespace value { + +static_assert(sizeof(quath) == 8, "sizeof(quath) must be 8"); +static_assert(sizeof(quatf) == 16, "sizeof(quatf) must be 16"); +static_assert(sizeof(quatd) == 32, "sizeof(quatd) must be 32"); +static_assert(sizeof(half) == 2, "sizeof(half) must be 2"); +static_assert(sizeof(half2) == 4, "sizeof(half2) must be 4"); +static_assert(sizeof(half3) == 6, "sizeof(half3) must be 6"); +static_assert(sizeof(half4) == 8, "sizeof(half4) must be 8"); +static_assert(sizeof(float3) == 12, "sizeof(float3) must be 12"); +static_assert(sizeof(color3f) == 12, "sizeof(color3f) must be 12"); +static_assert(sizeof(color4f) == 16, "sizeof(color4f) must be 16"); + +} // namespace value + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/xform.cc b/contrib/tinyusdz/tinyusdz_repo/src/xform.cc new file mode 100644 index 000000000..2e31bf6e1 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/xform.cc @@ -0,0 +1,1738 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#include "external/linalg.h" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include "math-util.inc" +#include "pprinter.hh" +#include "value-pprint.hh" +#include "prim-types.hh" +#include "tiny-format.hh" +#include "value-types.hh" +#include "xform.hh" +#include "common-macros.inc" + +// Use pxrUSD approach to generate rotation matrix. +// This will give (probably) identical xformOps matrix operation, but the resulting matrix contains some numerical error. +// https://github.com/PixarAnimationStudios/USD/issues/2136 +//#define PXR_COMPATIBLE_ROTATE_MATRIX_GENERATION + +namespace tinyusdz { + +using matrix44d = linalg::aliases::double4x4; +using matrix33d = linalg::aliases::double3x3; +using matrix22d = linalg::aliases::double2x2; +using double3x3 = linalg::aliases::double3x3; +using double3 = linalg::aliases::double3; +using double4 = linalg::aliases::double4; + +constexpr uint32_t kIdentityMaxUlps = 1; + +bool is_identity(const value::matrix2f &m) { + return math::almost_equals_by_ulps(m.m[0][0], 1.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[0][1], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[1][0], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[1][1], 0.0f, kIdentityMaxUlps); +} + +bool is_identity(const value::matrix3f &m) { + return math::almost_equals_by_ulps(m.m[0][0], 1.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[0][1], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[0][2], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[1][0], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[1][1], 1.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[1][2], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[2][0], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[2][1], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[2][2], 1.0f, kIdentityMaxUlps); +} + +bool is_identity(const value::matrix4f &m) { + return math::almost_equals_by_ulps(m.m[0][0], 1.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[0][1], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[0][2], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[0][3], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[1][0], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[1][1], 1.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[1][2], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[1][3], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[2][0], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[2][1], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[2][2], 1.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[2][3], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[3][0], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[3][1], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[3][2], 0.0f, kIdentityMaxUlps) && + math::almost_equals_by_ulps(m.m[3][3], 1.0f, kIdentityMaxUlps); +} + +bool is_identity(const value::matrix2d &m) { + return math::almost_equals_by_ulps(m.m[0][0], 1.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[0][1], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[1][0], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[1][1], 0.0, uint64_t(kIdentityMaxUlps)); +} + +bool is_identity(const value::matrix3d &m) { + return math::almost_equals_by_ulps(m.m[0][0], 1.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[0][1], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[0][2], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[1][0], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[1][1], 1.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[1][2], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[2][0], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[2][1], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[2][2], 1.0, uint64_t(kIdentityMaxUlps)); +} + +bool is_identity(const value::matrix4d &m) { + return math::almost_equals_by_ulps(m.m[0][0], 1.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[0][1], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[0][2], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[0][3], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[1][0], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[1][1], 1.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[1][2], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[1][3], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[2][0], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[2][1], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[2][2], 1.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[2][3], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[3][0], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[3][1], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[3][2], 0.0, uint64_t(kIdentityMaxUlps)) && + math::almost_equals_by_ulps(m.m[3][3], 1.0, uint64_t(kIdentityMaxUlps)); +} + +bool is_close(const value::matrix2f &a, const value::matrix2f &b, const float eps) { + return math::is_close(a.m[0][0], b.m[0][0], eps) && + math::is_close(a.m[0][1], b.m[0][1], eps) && + math::is_close(a.m[1][0], b.m[1][0], eps) && + math::is_close(a.m[1][1], b.m[1][1], eps); +} + +bool is_close(const value::matrix3f &a, const value::matrix3f &b, const float eps) { + return math::is_close(a.m[0][0], b.m[0][0], eps) && + math::is_close(a.m[0][1], b.m[0][1], eps) && + math::is_close(a.m[0][2], b.m[0][2], eps) && + math::is_close(a.m[1][0], b.m[1][0], eps) && + math::is_close(a.m[1][1], b.m[1][1], eps) && + math::is_close(a.m[1][2], b.m[1][2], eps) && + math::is_close(a.m[2][0], b.m[2][0], eps) && + math::is_close(a.m[2][1], b.m[2][1], eps) && + math::is_close(a.m[2][2], b.m[2][2], eps); +} + +bool is_close(const value::matrix4f &a, const value::matrix4f &b, const float eps) { + return math::is_close(a.m[0][0], b.m[0][0], eps) && + math::is_close(a.m[0][1], b.m[0][1], eps) && + math::is_close(a.m[0][2], b.m[0][2], eps) && + math::is_close(a.m[0][3], b.m[0][3], eps) && + math::is_close(a.m[1][0], b.m[1][0], eps) && + math::is_close(a.m[1][1], b.m[1][1], eps) && + math::is_close(a.m[1][2], b.m[1][2], eps) && + math::is_close(a.m[1][3], b.m[1][3], eps) && + math::is_close(a.m[2][0], b.m[2][0], eps) && + math::is_close(a.m[2][1], b.m[2][1], eps) && + math::is_close(a.m[2][2], b.m[2][2], eps) && + math::is_close(a.m[2][3], b.m[2][3], eps) && + math::is_close(a.m[3][0], b.m[3][0], eps) && + math::is_close(a.m[3][1], b.m[3][1], eps) && + math::is_close(a.m[3][2], b.m[3][2], eps) && + math::is_close(a.m[3][3], b.m[3][3], eps); +} + + +bool is_close(const value::matrix2d &a, const value::matrix2d &b, const double eps) { + return math::is_close(a.m[0][0], b.m[0][0], eps) && + math::is_close(a.m[0][1], b.m[0][1], eps) && + math::is_close(a.m[1][0], b.m[1][0], eps) && + math::is_close(a.m[1][1], b.m[1][1], eps); +} + +bool is_close(const value::matrix3d &a, const value::matrix3d &b, const double eps) { + return math::is_close(a.m[0][0], b.m[0][0], eps) && + math::is_close(a.m[0][1], b.m[0][1], eps) && + math::is_close(a.m[0][2], b.m[0][2], eps) && + math::is_close(a.m[1][0], b.m[1][0], eps) && + math::is_close(a.m[1][1], b.m[1][1], eps) && + math::is_close(a.m[1][2], b.m[1][2], eps) && + math::is_close(a.m[2][0], b.m[2][0], eps) && + math::is_close(a.m[2][1], b.m[2][1], eps) && + math::is_close(a.m[2][2], b.m[2][2], eps); +} + +bool is_close(const value::matrix4d &a, const value::matrix4d &b, const double eps) { + return math::is_close(a.m[0][0], b.m[0][0], eps) && + math::is_close(a.m[0][1], b.m[0][1], eps) && + math::is_close(a.m[0][2], b.m[0][2], eps) && + math::is_close(a.m[0][3], b.m[0][3], eps) && + math::is_close(a.m[1][0], b.m[1][0], eps) && + math::is_close(a.m[1][1], b.m[1][1], eps) && + math::is_close(a.m[1][2], b.m[1][2], eps) && + math::is_close(a.m[1][3], b.m[1][3], eps) && + math::is_close(a.m[2][0], b.m[2][0], eps) && + math::is_close(a.m[2][1], b.m[2][1], eps) && + math::is_close(a.m[2][2], b.m[2][2], eps) && + math::is_close(a.m[2][3], b.m[2][3], eps) && + math::is_close(a.m[3][0], b.m[3][0], eps) && + math::is_close(a.m[3][1], b.m[3][1], eps) && + math::is_close(a.m[3][2], b.m[3][2], eps) && + math::is_close(a.m[3][3], b.m[3][3], eps); +} + +value::quatf to_quaternion(const value::float3 &axis, const float angle) { + + // Use sin_pi and cos_pi for better accuracy. + float s = float(math::sin_pi(double(angle)/2.0/180.0)); + float c = float(math::cos_pi(double(angle)/2.0/180.0)); + + value::quatf q; + q.imag[0] = axis[0] * s; + q.imag[1] = axis[1] * s; + q.imag[2] = axis[2] * s; + q.real = c; + + return q; +} + +value::quatd to_quaternion(const value::double3 &axis, const double angle) { + + // Use sin_pi and cos_pi for better accuracy. + double s = math::sin_pi(angle/2.0/180.0); + double c = math::cos_pi(angle/2.0/180.0); + + value::quatd q; + q.imag[0] = axis[0] * s; + q.imag[1] = axis[1] * s; + q.imag[2] = axis[2] * s; + q.real = c; + + return q; +} + + +// linalg quat memory layout: (x, y, z, w) +// value::quat memory layout: (imag[0], imag[1], imag[2], real) + +value::matrix3d to_matrix3x3(const value::quath &q) { + double3x3 m33 = linalg::qmat( + {double(half_to_float(q.imag[0])), double(half_to_float(q.imag[1])), + double(half_to_float(q.imag[2])), double(half_to_float(q.real))}); + + value::matrix3d m; + Identity(&m); + + memcpy(m.m, &m33[0][0], sizeof(double) * 3 * 3); + + return m; +} + +value::matrix3d to_matrix3x3(const value::quatf &q) { + double3x3 m33 = linalg::qmat({double(q.imag[0]), double(q.imag[1]), + double(q.imag[2]), double(q.real)}); + + value::matrix3d m; + Identity(&m); + + memcpy(m.m, &m33[0][0], sizeof(double) * 3 * 3); + + return m; +} + +value::matrix3d to_matrix3x3(const value::quatd &q) { + double3x3 m33 = + linalg::qmat({q.imag[0], q.imag[1], q.imag[2], q.real}); + + value::matrix3d m; + Identity(&m); + + memcpy(m.m, &m33[0][0], sizeof(double) * 3 * 3); + + return m; +} + +value::matrix4d to_matrix(const value::matrix3d &m33, + const value::double3 &tx) { + value::matrix4d m; + Identity(&m); + + m.m[0][0] = m33.m[0][0]; + m.m[0][1] = m33.m[0][1]; + m.m[0][2] = m33.m[0][2]; + m.m[1][0] = m33.m[1][0]; + m.m[1][1] = m33.m[1][1]; + m.m[1][2] = m33.m[1][2]; + m.m[2][0] = m33.m[2][0]; + m.m[2][1] = m33.m[2][1]; + m.m[2][2] = m33.m[2][2]; + + m.m[3][0] = tx[0]; + m.m[3][1] = tx[1]; + m.m[3][2] = tx[2]; + + return m; +} + +value::matrix3d to_matrix3x3(const value::matrix4d &m44, value::double3 *tx) { + value::matrix3d m; + Identity(&m); + + m.m[0][0] = m44.m[0][0]; + m.m[0][1] = m44.m[0][1]; + m.m[0][2] = m44.m[0][2]; + m.m[1][0] = m44.m[1][0]; + m.m[1][1] = m44.m[1][1]; + m.m[1][2] = m44.m[1][2]; + m.m[2][0] = m44.m[2][0]; + m.m[2][1] = m44.m[2][1]; + m.m[2][2] = m44.m[2][2]; + + if (tx) { + (*tx)[0] = m44.m[3][0]; + (*tx)[1] = m44.m[3][1]; + (*tx)[2] = m44.m[3][2]; + } + + return m; +} + +value::matrix4d to_matrix(const value::quath &q) { + // using double4 = linalg::aliases::double4; + + double3x3 m33 = linalg::qmat( + {double(half_to_float(q.imag[0])), double(half_to_float(q.imag[1])), + double(half_to_float(q.imag[2])), double(half_to_float(q.real))}); + + value::matrix4d m; + Identity(&m); + + m.m[0][0] = m33[0][0]; + m.m[0][1] = m33[0][1]; + m.m[0][2] = m33[0][2]; + m.m[1][0] = m33[1][0]; + m.m[1][1] = m33[1][1]; + m.m[1][2] = m33[1][2]; + m.m[2][0] = m33[2][0]; + m.m[2][1] = m33[2][1]; + m.m[2][2] = m33[2][2]; + + return m; +} + +value::matrix4d to_matrix(const value::quatf &q) { + double3x3 m33 = linalg::qmat({double(q.imag[0]), double(q.imag[1]), + double(q.imag[2]), double(q.real)}); + + value::matrix4d m; + Identity(&m); + + m.m[0][0] = m33[0][0]; + m.m[0][1] = m33[0][1]; + m.m[0][2] = m33[0][2]; + m.m[1][0] = m33[1][0]; + m.m[1][1] = m33[1][1]; + m.m[1][2] = m33[1][2]; + m.m[2][0] = m33[2][0]; + m.m[2][1] = m33[2][1]; + m.m[2][2] = m33[2][2]; + + return m; +} + +value::matrix4d to_matrix(const value::quatd &q) { + double3x3 m33 = + linalg::qmat({q.imag[0], q.imag[1], q.imag[2], q.real}); + + value::matrix4d m; + Identity(&m); + + m.m[0][0] = m33[0][0]; + m.m[0][1] = m33[0][1]; + m.m[0][2] = m33[0][2]; + m.m[1][0] = m33[1][0]; + m.m[1][1] = m33[1][1]; + m.m[1][2] = m33[1][2]; + m.m[2][0] = m33[2][0]; + m.m[2][1] = m33[2][1]; + m.m[2][2] = m33[2][2]; + + return m; +} + +value::matrix4d inverse(const value::matrix4d &_m) { + matrix44d m; + // memory layout is same + memcpy(&m[0][0], _m.m, sizeof(double) * 4 * 4); + + matrix44d inv_m = linalg::inverse(m); + + value::matrix4d outm; + + memcpy(outm.m, &inv_m[0][0], sizeof(double) * 4 * 4); + + return outm; +} + +value::matrix3d inverse(const value::matrix3d &_m) { + matrix33d m; + // memory layout is same + memcpy(&m[0][0], _m.m, sizeof(double) * 3 * 3); + + matrix33d inv_m = linalg::inverse(m); + + value::matrix3d outm; + + memcpy(outm.m, &inv_m[0][0], sizeof(double) * 3 * 3); + + return outm; +} + +double determinant(const value::matrix4d &_m) { + matrix44d m; + // memory layout is same + memcpy(&m[0][0], _m.m, sizeof(double) * 4 * 4); + + double det = linalg::determinant(m); + + return det; +} + +double determinant(const value::matrix3d &_m) { + matrix33d m; + // memory layout is same + memcpy(&m[0][0], _m.m, sizeof(double) * 3 * 3); + + double det = linalg::determinant(m); + + return det; +} + +bool inverse(const value::matrix4d &_m, value::matrix4d &inv_m, double eps) { + double det = determinant(_m); + + if (math::is_close(std::fabs(det), 0.0, eps)) { + return false; + } + + inv_m = inverse(_m); + return true; +} + +bool inverse(const value::matrix3d &_m, value::matrix3d &inv_m, double eps) { + double det = determinant(_m); + + if (math::is_close(std::fabs(det), 0.0, eps)) { + return false; + } + + inv_m = inverse(_m); + return true; +} + +value::matrix2d transpose(const value::matrix2d &_m) { + matrix22d m; + matrix22d tm; + // memory layout is same + memcpy(&m[0][0], _m.m, sizeof(double) * 2 * 2); + tm = linalg::transpose(m); + + value::matrix2d dst; + + // memory layout is same + memcpy(&dst.m[0][0], &tm[0][0], sizeof(double) * 2 * 2); + + return dst; +} + +value::matrix3d transpose(const value::matrix3d &_m) { + matrix33d m; + matrix33d tm; + // memory layout is same + memcpy(&m[0][0], _m.m, sizeof(double) * 3 * 3); + tm = linalg::transpose(m); + + value::matrix3d dst; + + // memory layout is same + memcpy(&dst.m[0][0], &tm[0][0], sizeof(double) * 3 * 3); + + return dst; +} + +value::matrix4d transpose(const value::matrix4d &_m) { + matrix44d m; + matrix44d tm; + // memory layout is same + memcpy(&m[0][0], _m.m, sizeof(double) * 4 * 4); + tm = linalg::transpose(m); + + value::matrix4d dst; + + // memory layout is same + memcpy(&dst.m[0][0], &tm[0][0], sizeof(double) * 4 * 4); + + return dst; +} + +value::float4 matmul(const value::matrix4d &m, const value::float4 &p) { + return value::MultV(m, p); +} + +value::double4 matmul(const value::matrix4d &m, const value::double4 &p) { + return value::MultV(m, p); +} + +namespace { + +/// +/// Xform evaluation with method chain style. +/// so if you want to get RotateXYZ, +/// +/// xRot * yRot * zRot +/// +/// this is implemented in C++ as +/// +/// XformEvaluator xe +/// xe.RotateX() +/// xe.RotateY() +/// xe.RotateZ() +/// +/// NOTE: Matrix multiplication order is post-multiply in XformEvaluator for C++ readabilty +/// (otherwise we need to invoke xe.RotateZ(), xe.RotateY() then xe.RotateX()) +/// +class XformEvaluator { + public: + XformEvaluator() { Identity(&m); } + + XformEvaluator &RotateX(const double angle) { // in degrees + + value::matrix4d rm = value::matrix4d::identity(); + + double k = angle / 180.0; + double c = math::cos_pi(k); + double s = math::sin_pi(k); + + rm.m[1][1] = c; + rm.m[1][2] = s; + rm.m[2][1] = -s; + rm.m[2][2] = c; + + m = m * rm; + + return (*this); + } + + XformEvaluator &RotateY(const double angle) { // in degrees + + value::matrix4d rm = value::matrix4d::identity(); + + double k = angle / 180.0; + double c = math::cos_pi(k); + double s = math::sin_pi(k); + + rm.m[0][0] = c; + rm.m[0][2] = -s; + rm.m[2][0] = s; + rm.m[2][2] = c; + + m = m * rm; + + return (*this); + } + + XformEvaluator &RotateZ(const double angle) { // in degrees + + //double rad = math::radian(angle); + + value::matrix4d rm = value::matrix4d::identity(); + + double k = angle / 180.0; + double c = math::cos_pi(k); + double s = math::sin_pi(k); + + rm.m[0][0] = c; + rm.m[0][1] = s; + rm.m[1][0] = -s; + rm.m[1][1] = c; + + m = m * rm; + + return (*this); + } + + // From arbitrary rotation axis + XformEvaluator &Rotation(const double3 &axis, const double angle) { // in degrees + + // linalg uses radians + //double4 q = linalg::rotation_quat(axis, math::radian(angle)); + + value::quatd q = to_quaternion(axis, angle); + + double3x3 m33 = + linalg::qmat({q[0], q[1], q[2], q[3]}); + + value::matrix4d rm; + + rm.m[0][0] = m33[0][0]; + rm.m[0][1] = m33[0][1]; + rm.m[0][2] = m33[0][2]; + rm.m[0][3] = 0.0; + + rm.m[1][0] = m33[1][0]; + rm.m[1][1] = m33[1][1]; + rm.m[1][2] = m33[1][2]; + rm.m[1][3] = 0.0; + + rm.m[2][0] = m33[2][0]; + rm.m[2][1] = m33[2][1]; + rm.m[2][2] = m33[2][2]; + rm.m[2][3] = 0.0; + + rm.m[3][0] = 0.0; + rm.m[3][1] = 0.0; + rm.m[3][2] = 0.0; + rm.m[3][3] = 1.0; + + m = m * rm; + + return (*this); + } + + std::string error() const { return err; } + + nonstd::expected result() const { + if (err.empty()) { + return m; + } + + return nonstd::make_unexpected(err); + } + + std::string err; + value::matrix4d m; +}; + +} // namespace + +bool Xformable::EvaluateXformOps(double t, + value::TimeSampleInterpolationType tinterp, + value::matrix4d *out_matrix, + bool *resetXformStack, + std::string *err) const { + const auto RotateABC = + [](const XformOp &x) -> nonstd::expected { + value::double3 v; + if (auto h = x.get_value()) { + v[0] = double(half_to_float(h.value()[0])); + v[1] = double(half_to_float(h.value()[1])); + v[2] = double(half_to_float(h.value()[2])); + } else if (auto f = x.get_value()) { + v[0] = double(f.value()[0]); + v[1] = double(f.value()[1]); + v[2] = double(f.value()[2]); + } else if (auto d = x.get_value()) { + v = d.value(); + } else { + if (x.suffix.empty()) { + return nonstd::make_unexpected( + fmt::format("`{}` is not half3, float3 or double3 type.\n", + to_string(x.op_type))); + } else { + return nonstd::make_unexpected( + fmt::format("`{}:{}` is not half3, float3 or double3 type.\n", + to_string(x.op_type), x.suffix)); + } + } + + // invert input, and compute concatenated matrix + // inv(ABC) = inv(A) x inv(B) x inv(C) + // as done in pxrUSD. + + if (x.inverted) { + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; + } + + double xAngle = v[0]; + double yAngle = v[1]; + double zAngle = v[2]; + + XformEvaluator eval; + + DCOUT("angles = " << xAngle << ", " << yAngle << ", " << zAngle); + if (x.inverted) { + DCOUT("!inverted!\n"); + if (x.op_type == XformOp::OpType::RotateXYZ) { + // TODO: Apply defined switch for all Rotate*** op. +#if defined(PXR_COMPATIBLE_ROTATE_MATRIX_GENERATION) + eval.Rotation({0.0, 0.0, 1.0}, zAngle); + eval.Rotation({0.0, 1.0, 0.0}, yAngle); + eval.Rotation({1.0, 0.0, 0.0}, xAngle); +#else + eval.RotateZ(zAngle); + eval.RotateY(yAngle); + eval.RotateX(xAngle); +#endif + } else if (x.op_type == XformOp::OpType::RotateXZY) { +#if defined(PXR_COMPATIBLE_ROTATE_MATRIX_GENERATION) + eval.Rotation({0.0, 1.0, 0.0}, yAngle); + eval.Rotation({0.0, 0.0, 1.0}, zAngle); + eval.Rotation({1.0, 0.0, 0.0}, xAngle); +#else + eval.RotateY(yAngle); + eval.RotateZ(zAngle); + eval.RotateX(xAngle); +#endif + } else if (x.op_type == XformOp::OpType::RotateYXZ) { +#if defined(PXR_COMPATIBLE_ROTATE_MATRIX_GENERATION) + eval.Rotation({0.0, 0.0, 1.0}, zAngle); + eval.Rotation({1.0, 0.0, 0.0}, xAngle); + eval.Rotation({0.0, 1.0, 0.0}, yAngle); +#else + eval.RotateZ(zAngle); + eval.RotateX(xAngle); + eval.RotateY(yAngle); +#endif + } else if (x.op_type == XformOp::OpType::RotateYZX) { +#if defined(PXR_COMPATIBLE_ROTATE_MATRIX_GENERATION) + eval.Rotation({1.0, 0.0, 0.0}, xAngle); + eval.Rotation({0.0, 0.0, 1.0}, zAngle); + eval.Rotation({0.0, 1.0, 0.0}, yAngle); +#else + eval.RotateX(xAngle); + eval.RotateZ(zAngle); + eval.RotateY(yAngle); +#endif + } else if (x.op_type == XformOp::OpType::RotateZYX) { +#if defined(PXR_COMPATIBLE_ROTATE_MATRIX_GENERATION) + eval.Rotation({1.0, 0.0, 0.0}, xAngle); + eval.Rotation({0.0, 1.0, 0.0}, yAngle); + eval.Rotation({0.0, 0.0, 1.0}, zAngle); +#else + eval.RotateX(xAngle); + eval.RotateY(yAngle); + eval.RotateZ(zAngle); +#endif + } else if (x.op_type == XformOp::OpType::RotateZXY) { +#if defined(PXR_COMPATIBLE_ROTATE_MATRIX_GENERATION) + eval.Rotation({0.0, 1.0, 0.0}, yAngle); + eval.Rotation({1.0, 0.0, 0.0}, xAngle); + eval.Rotation({0.0, 0.0, 1.0}, zAngle); +#else + eval.RotateY(yAngle); + eval.RotateX(xAngle); + eval.RotateZ(zAngle); +#endif + } else { + /// ??? + return nonstd::make_unexpected("[InternalError] RotateABC"); + } + } else { + if (x.op_type == XformOp::OpType::RotateXYZ) { + +#if defined(PXR_COMPATIBLE_ROTATE_MATRIX_GENERATION) + eval.Rotation({1.0, 0.0, 0.0}, xAngle); + eval.Rotation({0.0, 1.0, 0.0}, yAngle); + eval.Rotation({0.0, 0.0, 1.0}, zAngle); +#else + eval.RotateX(xAngle); + eval.RotateY(yAngle); + eval.RotateZ(zAngle); +#endif + } else if (x.op_type == XformOp::OpType::RotateXZY) { +#if defined(PXR_COMPATIBLE_ROTATE_MATRIX_GENERATION) + eval.Rotation({1.0, 0.0, 0.0}, xAngle); + eval.Rotation({0.0, 0.0, 1.0}, zAngle); + eval.Rotation({0.0, 1.0, 0.0}, yAngle); +#else + eval.RotateX(xAngle); + eval.RotateZ(zAngle); + eval.RotateY(yAngle); +#endif + } else if (x.op_type == XformOp::OpType::RotateYXZ) { +#if defined(PXR_COMPATIBLE_ROTATE_MATRIX_GENERATION) + eval.Rotation({0.0, 1.0, 0.0}, yAngle); + eval.Rotation({1.0, 0.0, 0.0}, xAngle); + eval.Rotation({0.0, 0.0, 1.0}, zAngle); +#else + eval.RotateY(yAngle); + eval.RotateX(xAngle); + eval.RotateZ(zAngle); +#endif + } else if (x.op_type == XformOp::OpType::RotateYZX) { +#if defined(PXR_COMPATIBLE_ROTATE_MATRIX_GENERATION) + eval.Rotation({0.0, 1.0, 0.0}, yAngle); + eval.Rotation({0.0, 0.0, 1.0}, zAngle); + eval.Rotation({1.0, 0.0, 0.0}, xAngle); +#else + eval.RotateY(yAngle); + eval.RotateZ(zAngle); + eval.RotateX(xAngle); +#endif + } else if (x.op_type == XformOp::OpType::RotateZYX) { +#if defined(PXR_COMPATIBLE_ROTATE_MATRIX_GENERATION) + eval.Rotation({0.0, 0.0, 1.0}, zAngle); + eval.Rotation({0.0, 1.0, 0.0}, yAngle); + eval.Rotation({1.0, 0.0, 0.0}, xAngle); +#else + eval.RotateZ(zAngle); + eval.RotateY(yAngle); + eval.RotateX(xAngle); +#endif + } else if (x.op_type == XformOp::OpType::RotateZXY) { +#if defined(PXR_COMPATIBLE_ROTATE_MATRIX_GENERATION) + eval.Rotation({0.0, 0.0, 1.0}, zAngle); + eval.Rotation({1.0, 0.0, 0.0}, xAngle); + eval.Rotation({0.0, 1.0, 0.0}, yAngle); +#else + eval.RotateZ(zAngle); + eval.RotateX(xAngle); + eval.RotateY(yAngle); +#endif + } else { + /// ??? + return nonstd::make_unexpected("[InternalError] RotateABC"); + } + } + + return eval.result(); + }; + + // Concat matrices + // + // Matrix concatenation ordering is its appearance order(right to left) + // This is same with a notation in math equation: i.e, + // + // xformOpOrder = [A, B, C] + // + // M = A x B x C + // + // p' = A x B x C x p + // + // in post-multiply order. + // + // But in pre-multiply order system(pxrUSD and TinyUSDZ), + // C++ code is + // + // p' = p x C x B x A + // + // + value::matrix4d cm; + Identity(&cm); + + for (size_t i = 0; i < xformOps.size(); i++) { + const auto x = xformOps[i]; + + value::matrix4d m; // local matrix + Identity(&m); + + if (x.is_timesamples()) { + (void)t; + (void)tinterp; + if (err) { + (*err) += "TODO: xformOp property with timeSamples.\n"; + } + return false; + } + + switch (x.op_type) { + case XformOp::OpType::ResetXformStack: { + if (i != 0) { + if (err) { + (*err) += + "!resetXformStack! should only appear at the first element of " + "xformOps\n"; + } + return false; + } + + // Notify resetting previous(parent node's) matrices + if (resetXformStack) { + (*resetXformStack) = true; + } + break; + } + case XformOp::OpType::Transform: { + if (auto sxf = x.get_value()) { + value::matrix4f mf = sxf.value(); + for (size_t j = 0; j < 4; j++) { + for (size_t k = 0; k < 4; k++) { + m.m[j][k] = double(mf.m[j][k]); + } + } + } else if (auto sxd = x.get_value()) { + m = sxd.value(); + } else { + if (err) { + (*err) += "`xformOp:transform` is not matrix4f or matrix4d type.\n"; + } + return false; + } + + if (x.inverted) { + // Singular check. + // pxrUSD uses 1e-9 + double det = determinant(m); + + if (std::fabs(det) < 1e-9) { + if (err) { + if (x.suffix.empty()) { + (*err) += + "`xformOp:transform` is singular matrix and cannot be " + "inverted.\n"; + } else { + (*err) += fmt::format( + "`xformOp:transform:{}` is singular matrix and cannot be " + "inverted.\n", + x.suffix); + } + } + + return false; + } + + m = inverse(m); + } + + break; + } + case XformOp::OpType::Scale: { + double sx, sy, sz; + + if (auto sxh = x.get_value()) { + sx = double(half_to_float(sxh.value()[0])); + sy = double(half_to_float(sxh.value()[1])); + sz = double(half_to_float(sxh.value()[2])); + } else if (auto sxf = x.get_value()) { + sx = double(sxf.value()[0]); + sy = double(sxf.value()[1]); + sz = double(sxf.value()[2]); + } else if (auto sxd = x.get_value()) { + sx = sxd.value()[0]; + sy = sxd.value()[1]; + sz = sxd.value()[2]; + } else { + if (err) { + (*err) += "`xformOp:scale` is not half3, float3 or double3 type.\n"; + } + return false; + } + + if (x.inverted) { + // FIXME: Safe division + sx = 1.0 / sx; + sy = 1.0 / sy; + sz = 1.0 / sz; + } + + m.m[0][0] = sx; + m.m[1][1] = sy; + m.m[2][2] = sz; + + break; + } + case XformOp::OpType::Translate: { + double tx, ty, tz; + if (auto txh = x.get_value()) { + tx = double(half_to_float(txh.value()[0])); + ty = double(half_to_float(txh.value()[1])); + tz = double(half_to_float(txh.value()[2])); + } else if (auto txf = x.get_value()) { + tx = double(txf.value()[0]); + ty = double(txf.value()[1]); + tz = double(txf.value()[2]); + } else if (auto txd = x.get_value()) { + tx = txd.value()[0]; + ty = txd.value()[1]; + tz = txd.value()[2]; + } else { + if (err) { + (*err) += + "`xformOp:translate` is not half3, float3 or double3 type.\n"; + } + return false; + } + + if (x.inverted) { + tx = -tx; + ty = -ty; + tz = -tz; + } + + m.m[3][0] = tx; + m.m[3][1] = ty; + m.m[3][2] = tz; + + break; + } + // FIXME: Validate ROTATE_X, _Y, _Z implementation + case XformOp::OpType::RotateX: { + double angle; // in degrees + if (auto h = x.get_value()) { + angle = double(half_to_float(h.value())); + } else if (auto f = x.get_value()) { + angle = double(f.value()); + } else if (auto d = x.get_value()) { + angle = d.value(); + } else { + if (err) { + if (x.suffix.empty()) { + (*err) += + "`xformOp:rotateX` is not half, float or double type.\n"; + } else { + (*err) += fmt::format( + "`xformOp:rotateX:{}` is not half, float or double type.\n", + x.suffix); + } + } + return false; + } + + XformEvaluator xe; +#if defined(PXR_COMPATIBLE_ROTATE_MATRIX_GENERATION) + xe.Rotation({1.0, 0.0, 0.0}, angle); +#else + xe.RotateX(angle); +#endif + auto ret = xe.result(); + + if (ret) { + m = ret.value(); + } else { + if (err) { + (*err) += ret.error(); + } + return false; + } + break; + } + case XformOp::OpType::RotateY: { + double angle; // in degrees + if (auto h = x.get_value()) { + angle = double(half_to_float(h.value())); + } else if (auto f = x.get_value()) { + angle = double(f.value()); + } else if (auto d = x.get_value()) { + angle = d.value(); + } else { + if (err) { + if (x.suffix.empty()) { + (*err) += + "`xformOp:rotateY` is not half, float or double type.\n"; + } else { + (*err) += fmt::format( + "`xformOp:rotateY:{}` is not half, float or double type.\n", + x.suffix); + } + } + return false; + } + + XformEvaluator xe; +#if defined(PXR_COMPATIBLE_ROTATE_MATRIX_GENERATION) + xe.Rotation({0.0, 1.0, 0.0}, angle); +#else + xe.RotateY(angle); +#endif + auto ret = xe.result(); + + if (ret) { + m = ret.value(); + } else { + if (err) { + (*err) += ret.error(); + } + return false; + } + break; + } + case XformOp::OpType::RotateZ: { + double angle; // in degrees + if (auto h = x.get_value()) { + angle = double(half_to_float(h.value())); + } else if (auto f = x.get_value()) { + angle = double(f.value()); + } else if (auto d = x.get_value()) { + angle = d.value(); + } else { + if (err) { + if (x.suffix.empty()) { + (*err) += + "`xformOp:rotateZ` is not half, float or double type.\n"; + } else { + (*err) += fmt::format( + "`xformOp:rotateZ:{}` is not half, float or double type.\n", + x.suffix); + } + } + return false; + } + + XformEvaluator xe; +#if defined(PXR_COMPATIBLE_ROTATE_MATRIX_GENERATION) + xe.Rotation({0.0, 0.0, 1.0}, angle); +#else + xe.RotateZ(angle); +#endif + auto ret = xe.result(); + + if (ret) { + m = ret.value(); + } else { + if (err) { + (*err) += ret.error(); + } + return false; + } + break; + } + case XformOp::OpType::Orient: { + // value::quat stores elements in (x, y, z, w) + // linalg::quat also stores elements in (x, y, z, w) + + value::matrix3d rm; + if (auto h = x.get_value()) { + rm = to_matrix3x3(h.value()); + } else if (auto f = x.get_value()) { + rm = to_matrix3x3(f.value()); + } else if (auto d = x.get_value()) { + rm = to_matrix3x3(d.value()); + } else { + if (err) { + if (x.suffix.empty()) { + (*err) += "`xformOp:orient` is not quath, quatf or quatd type.\n"; + } else { + (*err) += fmt::format( + "`xformOp:orient:{}` is not quath, quatf or quatd type.\n", + x.suffix); + } + } + return false; + } + + // FIXME: invert before getting matrix. + if (x.inverted) { + value::matrix3d inv_rm; + if (inverse(rm, inv_rm)) { + } else { + if (err) { + if (x.suffix.empty()) { + (*err) += + "`xformOp:orient` is singular and cannot be inverted.\n"; + } else { + (*err) += fmt::format( + "`xformOp:orient:{}` is singular and cannot be inverted.\n", + x.suffix); + } + } + } + + rm = inv_rm; + } + + m = to_matrix(rm, {0.0, 0.0, 0.0}); + + break; + } + + case XformOp::OpType::RotateXYZ: + case XformOp::OpType::RotateXZY: + case XformOp::OpType::RotateYXZ: + case XformOp::OpType::RotateYZX: + case XformOp::OpType::RotateZXY: + case XformOp::OpType::RotateZYX: { + auto ret = RotateABC(x); + + if (!ret) { + (*err) += ret.error(); + return false; + } + + m = ret.value(); + } + } + + cm = m * cm; // `m` fist for pre-multiply system. + } + + (*out_matrix) = cm; + + return true; +} + +std::vector Xformable::xformOpOrder() const { + std::vector toks; + + for (size_t i = 0; i < xformOps.size(); i++) { + std::string ss; + + auto xformOp = xformOps[i]; + + if (xformOp.inverted) { + ss += "!invert!"; + } + ss += to_string(xformOp.op_type); + if (!xformOp.suffix.empty()) { + ss += ":" + xformOp.suffix; + } + + toks.push_back(value::token(ss)); + } + + return toks; +} + +value::float3 transform(const value::matrix4d &m, const value::float3 &p) { + value::float3 tx{float(m.m[3][0]), float(m.m[3][1]), float(m.m[3][2])}; + // MatTy, VecTy, VecBaseTy, vecN + return value::MultV(m, p) + tx; +} + +value::vector3f transform(const value::matrix4d &m, const value::vector3f &p) { + value::vector3f tx{float(m.m[3][0]), float(m.m[3][1]), float(m.m[3][2])}; + return value::MultV(m, p) + tx; +} + +value::normal3f transform(const value::matrix4d &m, const value::normal3f &p) { + value::normal3f tx{float(m.m[3][0]), float(m.m[3][1]), float(m.m[3][2])}; + return value::MultV(m, p) + tx; +} +value::point3f transform(const value::matrix4d &m, const value::point3f &p) { + value::point3f tx{float(m.m[3][0]), float(m.m[3][1]), float(m.m[3][2])}; + return value::MultV(m, p) + tx; +} +value::double3 transform(const value::matrix4d &m, const value::double3 &p) { + value::double3 tx{m.m[3][0], m.m[3][1], m.m[3][2]}; + return value::MultV(m, p) + tx; +} +value::vector3d transform(const value::matrix4d &m, const value::vector3d &p) { + value::vector3d tx{m.m[3][0], m.m[3][1], m.m[3][2]}; + value::vector3d v = + value::MultV(m, p); + v.x += tx.x; + v.y += tx.y; + v.z += tx.z; + return v; +} +value::normal3d transform(const value::matrix4d &m, const value::normal3d &p) { + value::normal3d tx{m.m[3][0], m.m[3][1], m.m[3][2]}; + value::normal3d v = + value::MultV(m, p); + v.x += tx.x; + v.y += tx.y; + v.z += tx.z; + return v; +} +value::point3d transform(const value::matrix4d &m, const value::point3d &p) { + value::point3d tx{m.m[3][0], m.m[3][1], m.m[3][2]}; + value::point3d v = + value::MultV(m, p); + v.x += tx.x; + v.y += tx.y; + v.z += tx.z; + return v; +} + +value::float3 transform_dir(const value::matrix4d &m, const value::float3 &p) { + // MatTy, VecTy, VecBaseTy, vecN + return value::MultV(m, p); +} + +value::vector3f transform_dir(const value::matrix4d &m, + const value::vector3f &p) { + return value::MultV(m, p); +} + +value::normal3f transform_dir(const value::matrix4d &m, + const value::normal3f &p) { + return value::MultV(m, p); +} +value::point3f transform_dir(const value::matrix4d &m, + const value::point3f &p) { + return value::MultV(m, p); +} +value::double3 transform_dir(const value::matrix4d &m, + const value::double3 &p) { + return value::MultV(m, p); +} +value::vector3d transform_dir(const value::matrix4d &m, + const value::vector3d &p) { + return value::MultV(m, p); +} +value::normal3d transform_dir(const value::matrix4d &m, + const value::normal3d &p) { + return value::MultV(m, p); +} +value::point3d transform_dir(const value::matrix4d &m, + const value::point3d &p) { + return value::MultV(m, p); +} + +value::matrix4d upper_left_3x3_only(const value::matrix4d &m) { + value::matrix4d dst; + + memcpy(dst.m, m.m, sizeof(double) * 4 * 4); + + dst.m[0][3] = 0.0; + dst.m[0][3] = 0.0; + dst.m[0][3] = 0.0; + + dst.m[3][0] = 0.0; + dst.m[3][1] = 0.0; + dst.m[3][2] = 0.0; + + dst.m[3][3] = 1.0; + + return dst; +} + +// ------------------------------------------------------------------------------- +// From pxrUSD +// + +// +// Copyright 2016 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "Apache License") +// with the following modification; you may not use this file except in +// compliance with the Apache License and the following modification to it: +// Section 6. Trademarks. is deleted and replaced with: +// +// 6. Trademarks. This License does not grant permission to use the trade +// names, trademarks, service marks, or product names of the Licensor +// and its affiliates, except as required to comply with Section 4(c) of +// the License and to reproduce the content of the NOTICE file. +// +// You may obtain a copy of the Apache License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the Apache License with the above modification is +// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the Apache License for the specific +// language governing permissions and limitations under the Apache License. +// +//////////////////////////////////////////////////////////////////////// + +value::matrix3d inverse_pxr(const value::matrix3d &m, double *detp, + double eps) { + double a00, a01, a02, a10, a11, a12, a20, a21, a22; + double det, rcp; + + a00 = m.m[0][0]; + a01 = m.m[0][1]; + a02 = m.m[0][2]; + a10 = m.m[1][0]; + a11 = m.m[1][1]; + a12 = m.m[1][2]; + a20 = m.m[2][0]; + a21 = m.m[2][1]; + a22 = m.m[2][2]; + det = -(a02 * a11 * a20) + a01 * a12 * a20 + a02 * a10 * a21 - + a00 * a12 * a21 - a01 * a10 * a22 + a00 * a11 * a22; + + if (detp) { + *detp = det; + } + + value::matrix3d inv; + + if (std::fabs(det) > eps) { + rcp = 1.0 / det; + inv.m[0][0] = (-(a12 * a21) + a11 * a22) * rcp; + inv.m[0][1] = (a02 * a21 - a01 * a22) * rcp; + inv.m[0][2] = (-(a02 * a11) + a01 * a12) * rcp; + inv.m[1][0] = (a12 * a20 - a10 * a22) * rcp; + inv.m[1][1] = (-(a02 * a20) + a00 * a22) * rcp; + inv.m[1][2] = (a02 * a10 - a00 * a12) * rcp; + inv.m[2][0] = (-(a11 * a20) + a10 * a21) * rcp; + inv.m[2][1] = (a01 * a20 - a00 * a21) * rcp; + inv.m[2][2] = (-(a01 * a10) + a00 * a11) * rcp; + + } else { + inv = value::matrix3d::identity(); + // scale = FLT_MAX + inv.m[0][0] = double((std::numeric_limits::max)()); + inv.m[1][1] = double((std::numeric_limits::max)()); + inv.m[2][2] = double((std::numeric_limits::max)()); + } + + return inv; +} + +value::matrix4d inverse_pxr(const value::matrix4d &m, double *detp, + double eps) { + double x00, x01, x02, x03; + double x10, x11, x12, x13; + double x20, x21, x22, x23; + double x30, x31, x32, x33; + double y01, y02, y03, y12, y13, y23; + double z00, z10, z20, z30; + double z01, z11, z21, z31; + double z02, z03, z12, z13, z22, z23, z32, z33; + + // Pickle 1st two columns of matrix into registers + x00 = m.m[0][0]; + x01 = m.m[0][1]; + x10 = m.m[1][0]; + x11 = m.m[1][1]; + x20 = m.m[2][0]; + x21 = m.m[2][1]; + x30 = m.m[3][0]; + x31 = m.m[3][1]; + + // Compute all six 2x2 determinants of 1st two columns + y01 = x00 * x11 - x10 * x01; + y02 = x00 * x21 - x20 * x01; + y03 = x00 * x31 - x30 * x01; + y12 = x10 * x21 - x20 * x11; + y13 = x10 * x31 - x30 * x11; + y23 = x20 * x31 - x30 * x21; + + // Pickle 2nd two columns of matrix into registers + x02 = m.m[0][2]; + x03 = m.m[0][3]; + x12 = m.m[1][2]; + x13 = m.m[1][3]; + x22 = m.m[2][2]; + x23 = m.m[2][3]; + x32 = m.m[3][2]; + x33 = m.m[3][3]; + + // Compute all 3x3 cofactors for 2nd two columns */ + z33 = x02 * y12 - x12 * y02 + x22 * y01; + z23 = x12 * y03 - x32 * y01 - x02 * y13; + z13 = x02 * y23 - x22 * y03 + x32 * y02; + z03 = x22 * y13 - x32 * y12 - x12 * y23; + z32 = x13 * y02 - x23 * y01 - x03 * y12; + z22 = x03 * y13 - x13 * y03 + x33 * y01; + z12 = x23 * y03 - x33 * y02 - x03 * y23; + z02 = x13 * y23 - x23 * y13 + x33 * y12; + + // Compute all six 2x2 determinants of 2nd two columns + y01 = x02 * x13 - x12 * x03; + y02 = x02 * x23 - x22 * x03; + y03 = x02 * x33 - x32 * x03; + y12 = x12 * x23 - x22 * x13; + y13 = x12 * x33 - x32 * x13; + y23 = x22 * x33 - x32 * x23; + + // Compute all 3x3 cofactors for 1st two columns + z30 = x11 * y02 - x21 * y01 - x01 * y12; + z20 = x01 * y13 - x11 * y03 + x31 * y01; + z10 = x21 * y03 - x31 * y02 - x01 * y23; + z00 = x11 * y23 - x21 * y13 + x31 * y12; + z31 = x00 * y12 - x10 * y02 + x20 * y01; + z21 = x10 * y03 - x30 * y01 - x00 * y13; + z11 = x00 * y23 - x20 * y03 + x30 * y02; + z01 = x20 * y13 - x30 * y12 - x10 * y23; + + // compute 4x4 determinant & its reciprocal + double det = x30 * z30 + x20 * z20 + x10 * z10 + x00 * z00; + if (detp) { + *detp = det; + } + + value::matrix4d inv; + + if (std::fabs(det) > eps) { + double rcp = 1.0 / det; + // Multiply all 3x3 cofactors by reciprocal & transpose + inv.m[0][0] = z00 * rcp; + inv.m[0][1] = z10 * rcp; + inv.m[1][0] = z01 * rcp; + inv.m[0][2] = z20 * rcp; + inv.m[2][0] = z02 * rcp; + inv.m[0][3] = z30 * rcp; + inv.m[3][0] = z03 * rcp; + inv.m[1][1] = z11 * rcp; + inv.m[1][2] = z21 * rcp; + inv.m[2][1] = z12 * rcp; + inv.m[1][3] = z31 * rcp; + inv.m[3][1] = z13 * rcp; + inv.m[2][2] = z22 * rcp; + inv.m[2][3] = z32 * rcp; + inv.m[3][2] = z23 * rcp; + inv.m[3][3] = z33 * rcp; + + } else { + inv = value::matrix4d::identity(); + // scale = FLT_MAX + inv.m[0][0] = double((std::numeric_limits::max)()); + inv.m[1][1] = double((std::numeric_limits::max)()); + inv.m[2][2] = double((std::numeric_limits::max)()); + // [3][3] = 1.0 + } + + return inv; +} + +/* + * Given 3 basis vectors tx, ty, tz, orthogonalize and optionally normalize + * them. + * + * This uses an iterative method that is very stable even when the vectors + * are far from orthogonal (close to colinear). The number of iterations + * and thus the computation time does increase as the vectors become + * close to colinear, however. + * + * If the iteration fails to converge, returns false with vectors as close to + * orthogonal as possible. + */ +bool orthonormalize_basis(value::double3 &tx, value::double3 &ty, + value::double3 &tz, const bool normalize, + const double eps) { + value::double3 ax, bx, cx, ay, by, cy, az, bz, cz; + + if (normalize) { + tx = vnormalize(tx); + ty = vnormalize(ty); + tz = vnormalize(tz); + ax = tx; + ay = ty; + az = tz; + } else { + ax = vnormalize(tx); + ay = vnormalize(ty); + az = vnormalize(tz); + } + + /* Check for colinear vectors. This is not only a quick-out: the + * error computation below will evaluate to zero if there's no change + * after an iteration, which can happen either because we have a good + * solution or because the vectors are colinear. So we have to check + * the colinear case beforehand, or we'll get fooled in the error + * computation. + */ + if (math::is_close(ax, ay, eps) || math::is_close(ax, az, eps) || + math::is_close(ay, az, eps)) { + return false; + } + + constexpr int kMAX_ITERS = 20; + int iter; + for (iter = 0; iter < kMAX_ITERS; ++iter) { + bx = tx; + by = ty; + bz = tz; + + bx = bx - vdot(ay, bx) * ay; + bx = bx - vdot(az, bx) * az; + + by = by - vdot(ax, by) * ax; + by = by - vdot(az, by) * az; + + bz = bz - vdot(ax, bz) * ax; + bz = bz - vdot(ay, bz) * ay; + + cx = 0.5 * (tx + bx); + cy = 0.5 * (ty + by); + cz = 0.5 * (tz + bz); + + if (normalize) { + cx = vnormalize(cx); + cy = vnormalize(cy); + cz = vnormalize(cz); + } + + value::double3 xDiff = tx - cx; + value::double3 yDiff = ty - cy; + value::double3 zDiff = tz - cz; + + double error = vdot(xDiff, xDiff) + vdot(yDiff, yDiff) + vdot(zDiff, zDiff); + + // error is squared, so compare to squared tolerance + if (error < (eps * eps)) { + break; + } + + tx = cx; + ty = cy; + tz = cz; + + ax = tx; + ay = ty; + az = tz; + + if (!normalize) { + ax = vnormalize(ax); + ax = vnormalize(ay); + ax = vnormalize(az); + } + } + + return iter < kMAX_ITERS; +} + +/* + * Return the matrix orthonormal using an iterative method. + * It is potentially slower if the matrix is far from orthonormal (i.e. if + * the row basis vectors are close to colinear) but in the common case + * of near-orthonormality it should be just as fast. + * + * For 4f, The translation part is left intact. If the translation is + * represented as a homogenous coordinate (i.e. a non-unity lower right corner), + * it is divided out. + */ +value::matrix3d orthonormalize(const value::matrix3d &m, bool *result_valid) { + value::matrix3d ret = value::matrix3d::identity(); + + // orthogonalize and normalize row vectors + value::double3 r0{m.m[0][0], m.m[0][1], m.m[0][2]}; + value::double3 r1{m.m[1][0], m.m[1][1], m.m[1][2]}; + value::double3 r2{m.m[2][0], m.m[2][1], m.m[2][2]}; + bool result = orthonormalize_basis(r0, r1, r2, true); + ret.m[0][0] = r0[0]; + ret.m[0][1] = r0[1]; + ret.m[0][2] = r0[2]; + ret.m[1][0] = r1[0]; + ret.m[1][1] = r1[1]; + ret.m[1][2] = r1[2]; + ret.m[2][0] = r2[0]; + ret.m[2][1] = r2[1]; + ret.m[2][2] = r2[2]; + + if (result_valid) { + (*result_valid) = result; + // TF_WARN("OrthogonalizeBasis did not converge, matrix may not be " + // "orthonormal."); + } + + return ret; +} + +value::matrix4d orthonormalize(const value::matrix4d &m, bool *result_valid) { + value::matrix4d ret = value::matrix4d::identity(); + + // orthogonalize and normalize row vectors + value::double3 r0{m.m[0][0], m.m[0][1], m.m[0][2]}; + value::double3 r1{m.m[1][0], m.m[1][1], m.m[1][2]}; + value::double3 r2{m.m[2][0], m.m[2][1], m.m[2][2]}; + bool result = orthonormalize_basis(r0, r1, r2, true); + ret.m[0][0] = r0[0]; + ret.m[0][1] = r0[1]; + ret.m[0][2] = r0[2]; + ret.m[1][0] = r1[0]; + ret.m[1][1] = r1[1]; + ret.m[1][2] = r1[2]; + ret.m[2][0] = r2[0]; + ret.m[2][1] = r2[1]; + ret.m[2][2] = r2[2]; + + // divide out any homogeneous coordinate - unless it's zero + const double min_vector_length = 1e-10; // + if (!math::is_close(ret.m[3][3], 1.0, + std::numeric_limits::epsilon()) && + !math::is_close(ret.m[3][3], 0.0, min_vector_length)) { + ret.m[3][0] /= ret.m[3][3]; + ret.m[3][1] /= ret.m[3][3]; + ret.m[3][2] /= ret.m[3][3]; + ret.m[3][3] = 1.0; + } + + if (result_valid) { + (*result_valid) = result; + // TF_WARN("OrthogonalizeBasis did not converge, matrix may not be " + // "orthonormal."); + } + + return ret; +} + +// End pxrUSD +// ------------------------------------------------------------------------------- + +value::matrix4d trs_angle_xyz(const value::double3 &translation, + const value::double3 &rotation_angles_xyz, + const value::double3 &scale) { + value::matrix4d m{value::matrix4d::identity()}; + + XformEvaluator eval; +#if defined(PXR_COMPATIBLE_ROTATE_MATRIX_GENERATION) + eval.Rotation({1.0, 0.0, 0.0}, rotation_angles_xyz[0]); + eval.Rotation({0.0, 1.0, 0.0}, rotation_angles_xyz[1]); + eval.Rotation({0.0, 0.0, 1.0}, rotation_angles_xyz[2]); +#else + eval.RotateX(rotation_angles_xyz[0]); + eval.RotateY(rotation_angles_xyz[1]); + eval.RotateZ(rotation_angles_xyz[2]); +#endif + + auto ret = eval.result(); + if (!ret) { + // This should not happend though. + return m; + } + value::matrix4d rMat = ret.value(); + + value::matrix4d tMat{value::matrix4d::identity()}; + tMat.m[3][0] = translation[0]; + tMat.m[3][1] = translation[1]; + tMat.m[3][2] = translation[2]; + + value::matrix4d sMat{value::matrix4d::identity()}; + sMat.m[0][0] = scale[0]; + sMat.m[1][1] = scale[1]; + sMat.m[2][2] = scale[2]; + + m = sMat * rMat * tMat; + + return m; +} + +// +// Build matrix from T R S. +// +// Rotation is given by 3 vectors axis(orthonormalized inside trs()). +// +value::matrix4d trs_rot_axis(const value::double3 &translation, + const value::double3 &rotation_x_axis, + const value::double3 &rotation_y_axis, + const value::double3 &rotation_z_axis, + const value::double3 &scale) { + value::matrix4d m{value::matrix4d::identity()}; + + value::matrix4d rMat{value::matrix4d::identity()}; + rMat.m[0][0] = rotation_x_axis[0]; + rMat.m[0][1] = rotation_x_axis[1]; + rMat.m[0][2] = rotation_x_axis[2]; + rMat.m[1][0] = rotation_y_axis[0]; + rMat.m[1][1] = rotation_y_axis[1]; + rMat.m[1][2] = rotation_y_axis[2]; + rMat.m[2][0] = rotation_z_axis[0]; + rMat.m[2][1] = rotation_z_axis[1]; + rMat.m[2][2] = rotation_z_axis[2]; + + bool result_valid{true}; + value::matrix4d orMat = orthonormalize(rMat, &result_valid); + // TODO: report error when orthonormalize failed. + + value::matrix4d tMat{value::matrix4d::identity()}; + tMat.m[3][0] = translation[0]; + tMat.m[3][1] = translation[1]; + tMat.m[3][2] = translation[2]; + + value::matrix4d sMat{value::matrix4d::identity()}; + sMat.m[0][0] = scale[0]; + sMat.m[1][1] = scale[1]; + sMat.m[2][2] = scale[2]; + + m = sMat * orMat * tMat; + + return m; +} + +} // namespace tinyusdz diff --git a/contrib/tinyusdz/tinyusdz_repo/src/xform.hh b/contrib/tinyusdz/tinyusdz_repo/src/xform.hh new file mode 100644 index 000000000..5cc6f8da4 --- /dev/null +++ b/contrib/tinyusdz/tinyusdz_repo/src/xform.hh @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: Apache 2.0 +// Copyright 2022 - 2023, Syoyo Fujita. +// Copyright 2023 - Present, Light Transport Entertainment Inc. + +// +// NOTE: pxrUSD uses row-major format for matrix, so use row-major format in tinyusdz as well. +// +#pragma once + +#include "value-types.hh" + +namespace tinyusdz { + +bool is_identity(const value::matrix2f &m); +bool is_identity(const value::matrix3f &m); +bool is_identity(const value::matrix4f &m); +bool is_identity(const value::matrix2d &m); +bool is_identity(const value::matrix3d &m); +bool is_identity(const value::matrix4d &m); + +bool is_close(const value::matrix2f &a, const value::matrix2f &b, const float eps = std::numeric_limits::epsilon()); +bool is_close(const value::matrix3f &a, const value::matrix3f &b, const float eps = std::numeric_limits::epsilon()); +bool is_close(const value::matrix4f &a, const value::matrix4f &b, const float eps = std::numeric_limits::epsilon()); +bool is_close(const value::matrix2d &a, const value::matrix2d &b, const double eps = std::numeric_limits::epsilon()); +bool is_close(const value::matrix3d &a, const value::matrix3d &b, const double eps = std::numeric_limits::epsilon()); +bool is_close(const value::matrix4d &a, const value::matrix4d &b, const double eps = std::numeric_limits::epsilon()); + +value::quatf to_quaternion(const value::float3 &axis, const float angle); +value::quatd to_quaternion(const value::double3 &axis, const double angle); + +value::matrix4d to_matrix(const value::quath &q); +value::matrix4d to_matrix(const value::quatf &q); +value::matrix4d to_matrix(const value::quatd &q); + +value::matrix4d to_matrix(const value::matrix3d &m, const value::double3 &tx); + +// +// | x x x 0 | +// | x x x 0 | +// | x x x 0 | +// | 0 0 0 1 | +// Remove [3][*](translation) and [*][3] +// [3][3] is set to 1.0. +value::matrix4d upper_left_3x3_only(const value::matrix4d &m); + +// Decompose into Upper-left 3x3 matrix + translation +value::matrix3d to_matrix3x3(const value::matrix4d &m, value::double3 *tx = nullptr); + +value::matrix3d to_matrix3x3(const value::quath &q); +value::matrix3d to_matrix3x3(const value::quatf &q); +value::matrix3d to_matrix3x3(const value::quatd &q); + +value::matrix4d inverse(const value::matrix4d &m); +double determinant(const value::matrix4d &m); + +value::matrix3d inverse(const value::matrix3d &m); +double determinant(const value::matrix3d &m); + +// Do singular matrix check. +// Return true and set `inv_m` when input matrix `m` can be inverted +bool inverse(const value::matrix4d &m, value::matrix4d &inv_m, double eps = 0.0); +bool inverse(const value::matrix3d &m, value::matrix3d &inv_m, double eps = 0.0); + +// pxrUSD compatible inverse +value::matrix3d inverse_pxr(const value::matrix3d &m, double *determinant, double eps = 0.0); +value::matrix4d inverse_pxr(const value::matrix4d &m, double *determinant, double eps = 0.0); + +value::matrix2d transpose(const value::matrix2d &m); +value::matrix3d transpose(const value::matrix3d &m); + +// NOTE: Full matrix transpose(i.e, translation elements are transposed). +// So if you want to transform normal vector, first make input matrix elements upper-left 3x3 only, +// then transpose(inverse(upper_left_3x3_only(M))) +value::matrix4d transpose(const value::matrix4d &m); + +#if 0 // Use transform() or transform_dir() for a while. +value::float3 matmul(const value::matrix4d &m, const value::float3 &p); +value::point3f matmul(const value::matrix4d &m, const value::point3f &p); +value::normal3f matmul(const value::matrix4d &m, const value::normal3f &p); +value::vector3f matmul(const value::matrix4d &m, const value::vector3f &p); + +value::point3d matmul(const value::matrix4d &m, const value::point3d &p); +value::normal3d matmul(const value::matrix4d &m, const value::normal3d &p); +value::vector3d matmul(const value::matrix4d &m, const value::vector3d &p); +value::double3 matmul(const value::matrix4d &m, const value::double3 &p); +#endif + +value::float4 matmul(const value::matrix4d &m, const value::float4 &p); +value::double4 matmul(const value::matrix4d &m, const value::double4 &p); + +// +// Transform 3d vector using 4x4 matrix. +// ([3][3] is not used) +// +value::float3 transform(const value::matrix4d &m, const value::float3 &p); +value::vector3f transform(const value::matrix4d &m, const value::vector3f &p); +value::normal3f transform(const value::matrix4d &m, const value::normal3f &p); +value::point3f transform(const value::matrix4d &m, const value::point3f &p); +value::double3 transform(const value::matrix4d &m, const value::double3 &p); +value::vector3d transform(const value::matrix4d &m, const value::vector3d &p); +value::normal3d transform(const value::matrix4d &m, const value::normal3d &p); +value::point3d transform(const value::matrix4d &m, const value::point3d &p); + +// +// Transform 3d vector using upper-left 3x3 matrix elements. +// ([3][3] is not used) +// +value::float3 transform_dir(const value::matrix4d &m, const value::float3 &p); +value::vector3f transform_dir(const value::matrix4d &m, const value::vector3f &p); +value::normal3f transform_dir(const value::matrix4d &m, const value::normal3f &p); +value::point3f transform_dir(const value::matrix4d &m, const value::point3f &p); +value::double3 transform_dir(const value::matrix4d &m, const value::double3 &p); +value::vector3d transform_dir(const value::matrix4d &m, const value::vector3d &p); +value::normal3d transform_dir(const value::matrix4d &m, const value::normal3d &p); +value::point3d transform_dir(const value::matrix4d &m, const value::point3d &p); + +// tx, ty, tz = [inout] +// default eps is grabbed from pxrUSD. +bool orthonormalize_basis(value::double3 &tx, value::double3 &ty, value::double3 &tz, const bool normalize, const double eps = 1e-6); + +// `result_valid` become set to false when orthonormalization failed(did not converged.) +value::matrix3d orthonormalize(const value::matrix3d &m, bool *result_valid = nullptr); + +// `result_valid` become set to false when orthonormalization failed(did not converged.) +value::matrix4d orthonormalize(const value::matrix4d &m, bool *result_valid = nullptr); + +// +// Build matrix from T R S. +// Rotation is given by angle in degree and its ordering is XYZ. +// (equivalent to [xformOp:translation, xformOp:RotateXYZ, xformOp:scale]) +// +value::matrix4d trs_angle_xyz( + const value::double3 &translation, + const value::double3 &rotation_angles_xyz, + const value::double3 &scale); + +// +// Build matrix from T R S. +// +// Rotation is given by 3 vectors axis(orthonormalized inside trs()). +// +value::matrix4d trs_rot_axis( + const value::double3 &translation, + const value::double3 &rotation_x_axis, + const value::double3 &rotation_y_axis, + const value::double3 &rotation_z_axis, + const value::double3 &scale); + +/// +/// For usdGeom, usdSkel, usdLux +/// +/// TODO: TimeSample support. +struct Xformable { + /// + /// Evaluate XformOps and output evaluated(concatenated) matrix to `out_matrix` + /// `resetXformStack` become true when xformOps[0] is !resetXformStack! + /// Return error message when failed. + /// + /// @param[in] t Time + /// @param[in] tinterp TimeSample interpolation type + /// + bool EvaluateXformOps(double t, value::TimeSampleInterpolationType tinterp, value::matrix4d *out_matrix, bool *resetXformStack, std::string *err) const; + + /// + /// Global = Parent x Local + /// + nonstd::expected GetGlobalMatrix( + const value::matrix4d &parentMatrix, double t = value::TimeCode::Default(), value::TimeSampleInterpolationType tinterp = value::TimeSampleInterpolationType::Linear) const { + bool resetXformStack{false}; + + auto m = GetLocalMatrix(t, tinterp, &resetXformStack); + + if (m) { + if (resetXformStack) { + // Ignore parent's transform + // FIXME: Validate this is the correct way of handling !resetXformStack! op. + return m.value(); + } else { + value::matrix4d cm = m.value() * parentMatrix; // row-major so local matrix first. + return cm; + } + } else { + return nonstd::make_unexpected(m.error()); + } + } + + /// + /// Evaluate xformOps and get local matrix. + /// + /// @param[out] resetTransformStack Is xformOpOrder contains !resetTransformStack!? + /// + nonstd::expected GetLocalMatrix(double t = value::TimeCode::Default(), value::TimeSampleInterpolationType tinterp = value::TimeSampleInterpolationType::Linear, bool *resetTransformStack = nullptr) const { + if (_dirty) { + value::matrix4d m; + std::string err; + if (EvaluateXformOps(t, tinterp, &m, resetTransformStack, &err)) { + _matrix = m; + _dirty = false; + } else { + return nonstd::make_unexpected(err); + } + } + + return _matrix; + } + + void set_dirty(bool onoff) { _dirty = onoff; } + + // Return `token[]` representation of `xformOps` + std::vector xformOpOrder() const; + + std::vector xformOps; + + mutable bool _dirty{true}; + mutable value::matrix4d _matrix; // Matrix of this Xform(local matrix) +}; + + +} // namespace tinyusdz diff --git a/doc/Fileformats.md b/doc/Fileformats.md index 5e7983812..a29b4d2bf 100644 --- a/doc/Fileformats.md +++ b/doc/Fileformats.md @@ -59,6 +59,7 @@ __Importers__: - [STL](https://en.wikipedia.org/wiki/STL_(file_format)) - TER - UC +- [USD](https://en.wikipedia.org/wiki/Universal_Scene_Description) - VTA - X - [X3D](https://en.wikipedia.org/wiki/X3D) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a59cafbbc..7b7fd850a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -148,6 +148,7 @@ SET( IMPORTERS #unit/utM3DImportExport.cpp unit/utMDCImportExport.cpp unit/utAssbinImportExport.cpp + unit/utUSDImport.cpp unit/ImportExport/utAssjsonImportExport.cpp unit/ImportExport/utCOBImportExport.cpp unit/ImportExport/utOgreImportExport.cpp diff --git a/test/models-nonbsd/USD/usda/README.md b/test/models-nonbsd/USD/usda/README.md new file mode 100644 index 000000000..e860175fd --- /dev/null +++ b/test/models-nonbsd/USD/usda/README.md @@ -0,0 +1,3 @@ +[blendshape.usda](blendshape.usda) copied from tinyusdz/models (No attribution/license cited in that project) +[texturedcube.usda](texturedcube.usda) copied from tinyusdz/models (No attribution/license cited in that project) +[translated-cube.usda](translated-cube.usda) copied from tinyusdz/models (No attribution/license cited in that project) diff --git a/test/models-nonbsd/USD/usda/blendshape.usda b/test/models-nonbsd/USD/usda/blendshape.usda new file mode 100644 index 000000000..06fc33063 --- /dev/null +++ b/test/models-nonbsd/USD/usda/blendshape.usda @@ -0,0 +1,154 @@ +#usda 1.0 +( + defaultPrim = "root" + doc = "Blender v3.4.0 Alpha" + metersPerUnit = 0.01 + upAxis = "Z" +) + +def Xform "root" +{ + float3 xformOp:scale = (100, 100, 100) + uniform token[] xformOpOrder = ["xformOp:scale"] + + def Scope "lights" + { + def DomeLight "environment" + { + custom color3f color = (0.05087609, 0.05087609, 0.05087609) + color3f inputs:color = (0.05087609, 0.05087609, 0.05087609) + float inputs:intensity = 683.0135 + custom float intensity = 683.0135 + } + } + + def Scope "materials" + { + def Material "Material" + { + token outputs:surface.connect = + custom string userProperties:blenderName:data = "Material" + + def Scope "preview" + { + def Shader "Principled_BSDF" + { + uniform token info:id = "UsdPreviewSurface" + float inputs:clearcoat = 0 + float inputs:clearcoatRoughness = 0.03 + color3f inputs:diffuseColor = (0.8, 0.8, 0.8) + color3f inputs:emissiveColor = (0, 0, 0) + float inputs:ior = 1.45 + float inputs:metallic = 0 + float inputs:opacity = 1 + float inputs:roughness = 0.5 + float inputs:specular = 0.5 + token outputs:surface + } + } + } + } + + def SkelRoot "Cube" + { + custom string userProperties:blenderName:object = "Cube" + + def Mesh "Cube" ( + active = true + prepend apiSchemas = ["SkelBindingAPI"] + ) + { + uniform bool doubleSided = 1 + int[] faceVertexCounts = [4, 4, 4, 4, 4, 4] + int[] faceVertexIndices = [0, 4, 6, 2, 3, 2, 6, 7, 7, 6, 4, 5, 5, 1, 3, 7, 1, 0, 2, 3, 5, 4, 0, 1] + rel material:binding = + normal3f[] normals = [(-2.3880695e-8, 0, 1), (-2.3880695e-8, 0, 1), (-2.3880695e-8, 0, 1), (-2.3880695e-8, 0, 1), (-0.23399627, -0.9436586, -0.2339963), (-0.23399627, -0.9436586, -0.2339963), (-0.23399627, -0.9436586, -0.2339963), (-0.23399627, -0.9436586, -0.2339963), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, -1), (1, 0, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0)] ( + interpolation = "faceVarying" + ) + point3f[] points = [(1, 1, 1), (1, 1, -1), (1, -1.9918684, 1), (1, -1, -1), (-1, 1, 1), (-1, 1, -1), (-1, -1, 1), (-1, -1, -1)] + int[] primvars:skel:jointIndices = [0, 0, 0, 0, 0, 0, 0, 0] ( + elementSize = 1 + interpolation = "vertex" + ) + float[] primvars:skel:jointWeights = [1, 1, 1, 1, 1, 1, 1, 1] ( + elementSize = 1 + interpolation = "vertex" + ) + texCoord2f[] primvars:st = [(0.625, 0.5), (0.875, 0.5), (0.875, 0.75), (0.625, 0.75), (0.375, 0.75), (0.625, 0.75), (0.625, 1), (0.375, 1), (0.375, 0), (0.625, 0), (0.625, 0.25), (0.375, 0.25), (0.125, 0.5), (0.375, 0.5), (0.375, 0.75), (0.125, 0.75), (0.375, 0.5), (0.625, 0.5), (0.625, 0.75), (0.375, 0.75), (0.375, 0.25), (0.625, 0.25), (0.625, 0.5), (0.375, 0.5)] ( + interpolation = "faceVarying" + ) + uniform token[] skel:blendShapes = ["Key_1"] + rel skel:blendShapeTargets = + prepend rel skel:skeleton = + uniform token subdivisionScheme = "none" + custom string userProperties:blenderName:data = "Cube" + custom string userProperties:blenderName:data:st = "UVMap" + + def BlendShape "Key_1" + { + uniform vector3f[] offsets = [(0, 0, 0.98508406), (0, 0, 0), (0, 0.892874, 0.98508406), (0, 0, 0), (0, 0, 0.98508406), (0, 0, 0), (0, 0, 0.98508406), (0, 0, 0)] + uniform int[] pointIndices = [0, 1, 2, 3, 4, 5, 6, 7] + } + } + + def Skeleton "Skel" + { + uniform matrix4d[] bindTransforms = [( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )] + uniform token[] joints = ["joint1"] + uniform matrix4d[] restTransforms = [( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )] + prepend rel skel:animationSource = + + def SkelAnimation "Anim" + { + uniform token[] blendShapes = ["Key_1"] + float[] blendShapeWeights = [0] + } + } + } + + def Xform "Light" + { + custom string userProperties:blenderName:object = "Light" + float3 xformOp:rotateXYZ = (37.26105, 3.163703, 106.93632) + float3 xformOp:scale = (1, 0.99999994, 1) + double3 xformOp:translate = (4.076245307922363, 1.0054539442062378, 5.903861999511719) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"] + + def SphereLight "Light" + { + custom color3f color = (1, 1, 1) + color3f inputs:color = (1, 1, 1) + float inputs:intensity = 5435247 + float inputs:radius = 0.10000002 + float inputs:specular = 1 + custom float intensity = 5435247 + custom float radius = 0.10000002 + custom float specular = 1 + custom string userProperties:blenderName:data = "Light" + } + } + + def Xform "Camera" + { + custom string userProperties:blenderName:object = "Camera" + float3 xformOp:rotateXYZ = (63.559303, -0.0000026647115, 46.691948) + float3 xformOp:scale = (1, 1, 1) + double3 xformOp:translate = (7.358891487121582, -6.925790786743164, 4.958309173583984) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"] + + def Camera "Camera" + { + float2 clippingRange = (10, 10000) + float focalLength = 50 + float horizontalAperture = 36 + float horizontalApertureOffset = 0 + token projection = "perspective" + double shutter:close = 0.25 + double shutter:open = -0.25 + custom string userProperties:blenderName:data = "Camera" + float verticalAperture = 24 + float verticalApertureOffset = 0 + } + } +} + diff --git a/test/models-nonbsd/USD/usda/texturedcube.usda b/test/models-nonbsd/USD/usda/texturedcube.usda new file mode 100644 index 000000000..980fa323c --- /dev/null +++ b/test/models-nonbsd/USD/usda/texturedcube.usda @@ -0,0 +1,101 @@ +#usda 1.0 +( + doc = "Blender v3.1.0" + metersPerUnit = 1 + upAxis = "Z" +) + +def Xform "Camera" +{ + matrix4d xformOp:transform = ( (0.6859206557273865, 0.7276763319969177, 0, 0), (-0.32401347160339355, 0.305420845746994, 0.8953956365585327, 0), (0.6515582203865051, -0.6141703724861145, 0.44527140259742737, 0), (7.358891487121582, -6.925790786743164, 4.958309173583984, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + + def Camera "Camera" + { + float2 clippingRange = (0.1, 100) + float focalLength = 50 + float horizontalAperture = 36 + float horizontalApertureOffset = 0 + token projection = "perspective" + float verticalAperture = 20.25 + float verticalApertureOffset = 0 + } +} + +def Xform "Cube" +{ + matrix4d xformOp:transform = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (-1.1853550672531128, 0, 1.9550952911376953, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + + def Mesh "Cube" + { + uniform bool doubleSided = 1 + int[] faceVertexCounts = [4, 4, 4, 4, 4, 4] + int[] faceVertexIndices = [0, 4, 6, 2, 3, 2, 6, 7, 7, 6, 4, 5, 5, 1, 3, 7, 1, 0, 2, 3, 5, 4, 0, 1] + rel material:binding = + normal3f[] normals = [(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, -1, 0), (0, -1, 0), (0, -1, 0), (0, -1, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, -1), (1, 0, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0)] ( + interpolation = "faceVarying" + ) + point3f[] points = [(1, 1, 1), (1, 1, -1), (1, -1, 1), (1, -1, -1), (-1, 1, 1), (-1, 1, -1), (-1, -1, 1), (-1, -1, -1)] + texCoord2f[] primvars:UVMap = [(0.625, 0.5), (0.875, 0.5), (0.875, 0.75), (0.625, 0.75), (0.375, 0.75), (0.625, 0.75), (0.625, 1), (0.375, 1), (0.375, 0), (0.625, 0), (0.625, 0.25), (0.375, 0.25), (0.125, 0.5), (0.375, 0.5), (0.375, 0.75), (0.125, 0.75), (0.375, 0.5), (0.625, 0.5), (0.625, 0.75), (0.375, 0.75), (0.375, 0.25), (0.625, 0.25), (0.625, 0.5), (0.375, 0.5)] ( + interpolation = "faceVarying" + ) + uniform token subdivisionScheme = "none" + } +} + +def "_materials" +{ + def Material "Material" + { + token outputs:surface.connect = + + def Scope "preview" + { + def Shader "Principled_BSDF" + { + uniform token info:id = "UsdPreviewSurface" + float inputs:clearcoat = 0 + float inputs:clearcoatRoughness = 0.03 + float3 inputs:diffuseColor.connect = + float inputs:ior = 1.45 + float inputs:metallic = 0 + float inputs:opacity = 1 + float inputs:roughness = 0.4 + float inputs:specular = 0.5 + token outputs:surface + } + + def Shader "Image_Texture" + { + uniform token info:id = "UsdUVTexture" + asset inputs:file = @.\textures\checkerboard.png@ + token inputs:sourceColorSpace = "sRGB" + float2 inputs:st.connect = + float3 outputs:rgb + } + + def Shader "uvmap" + { + uniform token info:id = "UsdPrimvarReader_float2" + token inputs:varname = "UVMap" + float2 outputs:result + } + } + } +} + +def Xform "Light" +{ + matrix4d xformOp:transform = ( (-0.29086464643478394, 0.9551711678504944, -0.05518905818462372, 0), (-0.7711008191108704, -0.1998833566904068, 0.6045247316360474, 0), (0.5663931965827942, 0.21839119493961334, 0.7946722507476807, 0), (4.076245307922363, 1.0054539442062378, 5.903861999511719, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + + def SphereLight "Light" + { + color3f inputs:color = (1, 1, 1) + float inputs:intensity = 10 + float inputs:radius = 0.1 + float inputs:specular = 1 + } +} + diff --git a/test/models-nonbsd/USD/usda/textures/01.jpg b/test/models-nonbsd/USD/usda/textures/01.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b24c7c708eb53fcffceee78207c2b01bcd2dfb38 GIT binary patch literal 10290 zcmeI0O-jR16ov0=n!iWUgod<6rNvTBi-qo>1EHV`aO_gtgmcHP!hs`K;LpUJ#E2cF zJzS(E1S}_+ymtsWFJI2RuW9jCd`Wb(yk3eiBBng0_>?R0+OFreJ>wWe9 zmGShkGN}&3+v=<`Zaq~dYr}`Twdvmzngb(Z2P6y}AQ6@tGEo#Z834m314#Z`lW8Q? zstMp*33UdG<{D+ti5-yaP|pk;P}Bql4#+TYfJFb5%2ESZjg*vX0?Szmb*7r;8fDOl z9gytMOc*$%s3;5^f>HltQFku}4k;?i{s*p+C)7<8?sc|Nnro0zud$gBJL1(8Vh1wY z?_LZXKvV?=4$$mP;2L?istIsi33cM7xkeduVh1GZ=a0UDFmOOo6Bsxk!@vO&mKq?@ JKY16$`!{4&2?ziH literal 0 HcmV?d00001 diff --git a/test/models-nonbsd/USD/usda/textures/checkerboard.png b/test/models-nonbsd/USD/usda/textures/checkerboard.png new file mode 100644 index 0000000000000000000000000000000000000000..fde967aca605b21710638f3049ffb43924cb094f GIT binary patch literal 7688 zcmeAS@N?(olHy`uVBq!ia0y~yUi(1B2{d zPZ!6KiaBp?9u#CU;9++3`ET2x;LIN{m5Yw8Dj{w_cBFZf%m zJkT75{|XI^3=AFu2Y_r9jszen#9{y>IhiCF7#dg@c^DWRni$v^7!({Cm>C!*j4B@u znbAZrnmtBKfNFtGfL<}Zo_GB+q30CIIWK;12FCg5zzfhJJwj0%s2$!LNY%?(5r1&oPDbidtQ Tws0n}6k+gm^>bP0l+XkKh(xEg literal 0 HcmV?d00001 diff --git a/test/models-nonbsd/USD/usda/textures/texture-cat.jpg b/test/models-nonbsd/USD/usda/textures/texture-cat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..22870c622e0871e051f47977a51dab3d9bec3c22 GIT binary patch literal 50234 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%0A*%-6<9 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

-2^k z7Q9`q64FDd-EV?yl9^6#$cMUYmoLxiuN>+Uy4NH#o!*ecPP@pRwscmyJ$}SYGSle| zd2)zn_Vy8R$}gKra+7S;=?%I4Ao_cx%^N5qd--vbEbH`!JioJz^_*QpiC^!^O|q=h z8}j;fl{sz7`gQAMlT2DoZ^${03B1p8AH_qSmu-?2o!*eIZja@614}3uZalG?WJRYp zau!*uGdIbyPH)JY2hC)q(&w;0qkD6cEbH`! z+^HOtPZW8?xs?H}2b{lPv4>hU_B`XMgP2*o64>-2^k)cb|CSxQg#+oU0iNzT^k4Y|orU-L!#zhgamc7UgYE&y!8EqSG7lo8Eo+oTR<1dPG5Hk`?*2Mvk`8#)vaHh^a%{8L*3sp^lH1-H%uRB(PH)H?W;?KN=UUmK zfy1~-mUVhVK7Mj5E7zqy_hSCsB+ELzA@5moo?T4zW%vG0;U-zu=?&TQZ8%%9sfN<0 zL?}1OvQBTvbzO>cX>&O_VnP#ck`ojJ>Kw#60+Z~ z)woGkbb3QRUDnE{ckoc0yB%jHS<&eYSsAmL#ceFj^1b_UlPv4>hFmAD7pvAQPFX}h z2QbO9PH)JqTi2BDE5mu8<`=Cdnd$U~{Ptlr>)v4}s^ z#_XJBeHD`|>-2^^VEJ8ka(Z9+yJlY~CRx_$4f$|valRpPu>5m=BgG^uI=vxx8{d$R zNFOZsb1AF*2P-efd9F)@lBgznfZ!-|8R5=UiGTn`A|&H{>QI zB>wl>1b%MMcd|)Vbb3S1ZSe!!7k7z$`_<=)NtShbLoOK^#`jC#vB2$R6qBsz^oE=@ zOJy|@I4`>Kmi!+q>oot$86&r`XPzCD!cPt`lPv4>hU~HbZ+3Ikm&(CKHJC}3b$Ua7 z+F(2@b@6LOE^ygul4YIVkeBuu!5+05qU>sum2Hw`o!*e;oljVadR>(ZUBAvY$+Avw z$XTD4;x%W7DWQKakxjCq(;ISN>V1~mG*FrNQzdSaWu4xTOHZCG&+~pSXKzd7CYkB< zhP>H3QMr4?L8-o?H#5mxr#Iw&y-jL^OrD$if^j8qO@+MaEa6`kIYXZ~=N?V1&(JlghxnPgd~H{|1)Ex6kFCt0oX zRyN6sPH)Iv4*BxGrq)*c+Mkn6vZB))@`Yx__?xWiN`+omWs|Jv^oCqJc?2IDInY|- zmoV8RD>}U)FZ4_0eHQ0fn>rkqO|qiX8}jnlF+6$FTq|vR|AQ5s=3lu|y@ou?J6f(a zwxVK^6`kIYJNJm=89V;Wo|#!(G0BQfZ^-F~YxAgW&$4N+m}Et#H{@4?!`Ysm3D%gn z5!@upI=vw;=+&S9Fu+m%R{fEgWJRYpoH)Cjs6Zjxo4-jKf-x0Q{U*In8D=PYKDWu4xTD+HWm zcT##QlYj8cHp#M1Z^$n;jbknk1}n5>n`Bw1H{|5>vDSJ+#wfwhs_iw&**d);zpT4k zE?T{WQmsxXH_1$=H)dX%SICS}nl|*dzBI>*PH)IN-ri+>LcXNmMcP6($+Avw$X{Ks z@Fu&GluETb$R=6Q=?yt~sS96TrIlhWF_D>MMW;99eKQuazuY4g+Z12^A1v!M|H=!d z$kqkdvaF>FjaE!@woY%z?iD2dV4=g_!ArhSOtPZW8*=!ze!Trp)8txP#>ggF(diBO z;FY$#j}mA-_@_HJ$%;;I$nR==B`^DKJR5$hKR3xtr#Ivy9q!8^D;?R-tSdLkOs6;G z+Gh(W@w?jb`^QS{HOXA3H)Nm8#`3&6BlxqPzwR~3Os6;G$CZC);SRm|-hEfX|AS?n z=3jYSrC4QB?G<}}oKT9JWUkX2a;=x{{8-<3UN5wbY?2k7-jMT?M#-~|&u4{N_Ek(W z)9DTQ1O5Xi(oH5wQS^I0D$)L?{D~#{H=s2szRybkP?YvyPm24}ac7Eq(jL5>@`LDj z-(E@DPCgn_JdENJ=|x;)ipNsijrJlAo<_&bDK5%0jpE>VI%dM3eBvn%4yJeoii`3D zQyiR0$CW5Pl+I^ne_}5>7P%tsMS8F}7S|H{p%e$BJU>z#zIy3o9wd3!NN=QmnZ}JOECINlmY!E*psd&`bgw?(jFX4 zaW6U+^$n&y*o)#-?QszU=a4Sa9?y|KaW2JU?D1TRgYQyYlz9W~?@}Br+My-I_t9R| z7d)AcTif-MDGu&skBf5lvd0BOA1v4&7cBe}?d1{v8dF?cD~HAft}7Vj6uEavFUl!c zd_lq zj}&*I*g)ETq&PT?;)N(Sl(sO6gGJeVDK?U}o1_swUUXiR16+nnk~_tNY5tX=Jy_K5 zF6sO!PxKGCBgOscShRUZ+Joy-T#V7T6t7Era2_4kq~kz3pC^CfEQ*UhillfJ#lgW8 z7h^b#;=vRLXHZ*5P+a(BP+7s1=s1Sr=V@Pw^k9jO#T;Bpdx_#;(MMv;{O=mIkK&;e z&j_b;qz8-ZiTNe2FUk)VbF%`)Bj~)SE4Z9JzL4VO>~X=O{*~!iTt|#G@KrhvqvM;j zzbb#?3_2Ed&!K$=#lfOaMO%sb+@vxI{+Lf~0v2;d^g&zNawrZKbr55%D{Z0;!NODY zQFq$JN3?A$T~mBSTZu7qnsNpE3#MGK#QxFu5_;N1uHY?p{anFzz2FnW~Iu_&YD(yE=96XJVh2}Et#kc{BW5FkAf7u=v?8WW(TfuZ*tbyB! zi&9*))m*`}2m4aIIvvMS+?V#?Tsju(u2>IpSEg-@(KE_kRtE(x~B1q(0H*J7O(PcF50~k#l<)U z2U7ez#l+n z3vo+3XA%3@c_y)_UwiWCOxGO1{+Z9i^`v%yb;*}^zBCY5La5;)!6>)lp73&RH^jBp%*O|(+fpWm2+^6YyJ8c7K z1BX$3JRP5>EsQqsT#6r}W3fKYr9C)<;z#LNqId@F!9P;`1ReLL_>Z&)dr^EZ9g8{X zMSHMB@m+N6PH|B;a3zZ0q~n1UuS9!rXNq5?8MW zNu>9py_k0z)3I3Fg6wgy@QEew2-*uDjp_I<#l`#qF_a|LV+D_93zDn_A+F~hol{Rp9@)wPO9GpdQaSbt#vM3JTNb$=Q&!TN3#lg$$aR=Hjv&RLG zqb-xPxwMU=ICw0@=ThuEZDT18UP$qy6uU{=LW+Y|Qe4zY=vGo3yu=>Qq;f8?#|4k4 z_)^jaQ@zGh9GpmTH;Rk(C6VIbz7+3F@m;j_r8syX#lidFE3bEZS4Vw$LWp6P!$OQ3g@I zWQv2^QhXZ4YSPx0;^0VoT$Cr$9v3Xeq0np*Y>x{T{vs~ki-kWp9&u`ic*Kdt{GCa8 z;?scQ;4q4dvWRvIqqxQtkDz!k<%l>~%r8-wc-qAL0*i4znPNj}6XRTCif2)5fISWt zZz0D?Sq`4i@86w27#%7@y!l z6c^VN<75!U!J<7y8E=xmXix9}if<4;6dyowa0bOcj%{&{;^4s)-%c^n4udHU9!v3k z6dOR>Sc-!uQT!0aM1N1BICuudk5Vj|wiy%$PqoLz_?v2v3l{T2w8>4u6bENgT&%Y_ z6wjnMcr(Q$+7?oLGsVF_Qp}CE&Q#wYDGnC>DEeYc_CNgyZc1^{R+219O-T>#NO4gY zv2~<4m>lf?aqvvP?z~f>V%+amCvJUNlt)+Z$hUSY%-_uF$ph;Y<|{tyz|*ro=UMeT z@ewUs^MYyJ_@uPfJb7jh-n(CWO^3X0U$*B(CpdFyW*@#f)`dH~Oy>FHyJ)H_v0ItO96rcHy^vS#NqauOToryes~u0jSewJAQfk;dy(YR#XI>&bgAY{}sRUtAA)qWsDCe6)J(@f^bU4Qa>WgFMs= zd6;jQH}Jtc!MuSFd{J)9WB9-qd6>7T2YiqRAIZ{_KUrR!hwtsiZ`>@#mrm=!<83Xp z`okw6s2i`pFo7cv?dX}@gLi2i%;8fzpa-876Q{Kst`A@2p&!4G>&E-PEW_c`KcpM) zG%iN#H?%)|k%w`7{$(fn{bFS}d|sdK!~<8yXyX>+7{18EJPIk@k+0ibio>VI^$y&* zcbqnFF#h3-Jgg(neLB*=FDTC8Q*>nq{&qkMZQZ~+0$=1|9s9EUm;8J|e-586Q#$d6 z_2|ESU|)Z+{=gS`=tuM$e9%wmH~7G}V{jL~=Uhn+J@SzU-PpsOdBnHPv~od@e4K|K z<->Wj2kM3MXb0p&kM>6%bZGbg)uX?W2OawPfAtt&$b$~!>VNeZ&&Y!gM$-7?NsQJJbd22J@Gq+&G)|UAI=Z7UEOdz0-W)i@d*Xyk-f#-dPXse8A$WT-SUqIuz%3{6pBL zJ>_{ydS zPRpxSu^QiVF^wImR+;Zu{f*|c^|H(wG#ba64&TMbe)pB;17GB=39iQn-e#rb5hN-0w`ANV3~QK34#hq8?2(RY0AW0z|_M^9~IV+$?}5H$`1*3|wDv~(!w>mr?@fhHv*JxQF!+5nCxlR$Eb;bO2A{drOY&|bhida3 zKJY~z=6iCRE39<8;S4^FH~8?3uH&`!2I~)ek%#pr-1EHeE4DfVZ33yW_~Edm-?^N#xuqn#^1$&H!NZAO7_3Hn>C$z zt4H(MH?s?IDR3UcF?^9XdHfr;cEvocyeR+w=0S(@qdxF0G|`36X49=h=)SkI&{XYy z_rLc;oOh_=$WMh$x8lCGUv=ecYcH_E2YI+3LWexI)xzsnUSP$2?P`uIcPYHh3LoU* zeh3}%dMg$l;=0X>`x@?B4{E#dT1On1t)m-{3f-;U$70)9_`7PmtuIDdcZA)(4*YYlUT?z!!PdIy&$r^`jW{@Od)EiI1q> zm|eW*#E+b}YCg~*59guBdGrhFi~1M3=ETDI*&2V~@Phx30Xvz8Wn2x)c9x zb_o0PkcAKXt%l|UJp@9Mkq<-akl9zDNt;Lmq6hV#&)UT8PuHJ#_i z|6CcawWAth;bStx8GMk3c16D-ujEWO?(-p3>&M;IEPR_=9R?rdpkcaxCJy3u6 zAP?<|dLbX>Kz(ps%$M%$9d@ChjD=MhUZqSlUV05-k`^M zSU)h1;fv@0jHUkkT&5dC9(tR%Kr? z1|Rt1{Q>QcdczO-XfM1!zz^%5eqHOmf4#M0FIS$EzwzV!OdR7p*1<-x4t&*{joN(& zKFIqk&6Q6WzSs&K@^Bw=jdbAE(-(ib@7eEz$eZTt!ZUizv_gkG+y~Q_I`Z>fXKMFd z+&_^wnbviBF0(?1JluzI9(&}y8t=?+e7oqQZ=l~ot9w-^et6O%%?EnnBhDiaJf> z?VAl%8l|NA9_4r;dyIiXCJ=w-E@{d#+#3O zGmqU^{fymNJ%izS3(sGHYo4>x8|G`zd+NH2>zzDh8jSJI}KHht_EC zcX&^OFY@qyc{#2m4}ZIs!RPgeKiGin>$UewyeGmJd3f(_el36>I=zm;=hs2E*xZ&I zwD(-Rr@|L`7>5{l@WD94xWoH0e4#_$z|dk``96zf6b|HSAa9~$N#1wJ8U`KmQ0|`3|6rTTeQGDv7kRCQ1@qXzECwC&&~6)kJIA8l zf9fZ+H}b{~D9`KNS<0Y89{Mdj^*2`K!>4hA{zhKw*b4mAx&`!I&p$fkVcZ<~<_Mer z(;{u0V!R>GdZ9e`EkBDvhdhkil)6XR+xN4zd4lndykX&``PPe*8Fa|Qyy@Hi4D$|~ zqRmsxH{`irEWtl}G=V{fJj~lS)<4*<4wJNX0`nbt15Wz!BxMSN4tZEN3jOeaNi(Nv z>lD@-t^H^zWj8TWenr+X_?3D+2_l(brj0@>+a)GvvV?Bm1^03~b-0(p;QEuE1;EV6U@V!~_%S8sCUQWkZ@aZes_k{S~5WdL6_m)?yea8Pdewe{$*yr2Wr0d7D?ejoWW;M`Nix|xh(B_ zT6}K{U*zF?0tmMYZR{UHAKcCUg+w*aZ^Z0oU^6_&O_~5#^R}1lrYdTt?Lmqx!^T)MT9r=>*3ph__+#vkT+pL3I48WrWHEm;pa66iu}PA z|GeA^J=zWJk9@R0+7W*Ln~!p%{_sQnQ4i$9FTZavFIV`k71zV{F^+J3TyJ9dS$6N- z9c^6UeG}~fANV2<^~U=qeDJ=xr+jH1Ip3Le`o0wZDWNFC`{LCL=h*x$MVWQyY1V(Y zv-ZA;_eESE*Mkpyk%#Ny`gm`}`zo%7>t{^~;vF}ZWw<`BhxgUZZWr0cI^`Jhp~w8i ze8&8R9{KqB**6sia-UZ#t+_Y5@~|H7tlxh5Id2+q!n(Ctciym0F>A)yhRko{UhA^$ zUhH+p`_`5}udt5XKGf>VVW9h2=#`PWHXUc42&1WpJ!UuVay6j;Iuhxe{hdhiM ztVdXX;DbD@N9Z@?!3X__Joum=(XUu%v0kQsAIbOMie``0I9~s88nd}gWu+#@FszqY zcc*&{VFBv~vdeIvrQR;dE0SYSj^~r>yD`_SfSK%jQ#X8V-K!aXO6$d zjui{F_IkWW(;?3-sW*3Xp1@|l9>c439?L>D9kaS^nXLIBPprX%*NxY7TAsZgs29o& zALOAPdAB&9-C@0Uzgt^9ivQram`!kLO5Y!4vh9CQX3vf-W4Pb_GHnif-18@P@9a>v zZ_{c`hrB+!;`#2e)7jN!$-GS1eD-s-_RPsLTk}C)&$D&eo(fAf9rC)gPUf>C#5$j%aR+`QE0uNf>Bh@->CevZsK6Xz`)NMN zTe9ATIW$dYbLsDgxm<3--dH;GR%NYP9(cWQxGwyV zkM>7@zz=!of7Ba3C@;nleBg^bj6bwHe9&GPfAE1X@)FxN<86EF*7_4Z=&wtYzGeY; z4r=2VzL)9mu$^jsfMFbA+`td{7&mcEzGaaWPcZl`ADGOGbiL1@M|m)>P;Tg3?6$IV zf4rC>O2^J@QeGFK#7p%bI5lKFGs)=o5;3!6F|^9M6?lx3ErO{$O3gI)!m;BNh{E&~lip>-G)$^Sh<_&x>-@-08V~bCAWAM>@ z?ehim1NrbnKIR?f3;eLY<9@ThZ5*F|F2{O4q#j@BTbMb{AIXN-eWcxQu>Ox+`~wT$ zUx;CShaUM${o;5^UU4lSdaVD=Z;W8knSt7KHSW{!fiLoK-^P6dKFGs!0`61r!F>tO z4e)_4^k@g<;XLXKA6yU57q~9`@mzuHU_D#ksVxsE8O;2D>cH>%(tkV5zP_FMwFdJY z>dKn5i(_Nn1ZnFT)+4NE&?6uBMdU+|^$hn(^aJ_@`6v(a(a-1?ln3Ji&ojt}AM)`$ zgXa?Xp`6Hr5A?`KdHv^FctTcB{<_OGmKT`9rFA!1;*m}~x6cRWu&WDi{mTmWBC-=- zvN4OLtn1D%lpV!7Z0*5IZxK73PbM}C_A{!O{4E_}$P`t;pvdrgo0 z-p&2l$vIzYI^=l<73IE>&G`8pK|FR%Q@-*{HFj@7oaTePfJ6`0dUvd*LtcUDp8QW= z&YL89@n_*y{%CCyLdWhMEp}bA2U#qjp`MSJHtyEUm?FHXqL>2G!Ag zkk_Jj20JwLGfjuQ8S`#3)~GtqSazL_-cX5`Ny=is%&4aMAkS~+YSvt>$dQ+rZsFxi zHRQYg{)_Dht&*Lw3!x9fzN1`-RN^i?-T0#<+za z);-Kyx1-JY7xn6}wqs)XiWi)9Z`hyhxlo&7o?;z5d}}0IFq5+=`X2I5Rtc@Y(Qm&7 z9b5+|4$P?x@XYlo#V})9&l6_N{sx<+Mu zxI_7`6}Zm4jyK@EB=3*Gu>y591IzF451wzmDfe zUbSL%8?>SC58Kku#{PLe+4Ws>w)8^>R`FU}cB6P3?Rf;x6UfK78Pu@_FXwJ$@Z0>T zC%ZSiA;UPrxPc$?Fz%6$JdA(jqh6?I`pSDO>q85^`pXw=N~tzH{D<$^UkNQZ>Wz8^ z%$mstw`$FihjuPj`3Ac%s|klswP9H-@J)=??(l&x^3cDto?m8beHwB2>@2vRIZuqx z`WO8TU*utYeQ0o#b$iY^eEOdFfgNeawef{<0$=1|oc49U%PMtm$l>#OolMqjQv+?> zVw}Pkd6*vo$8NF8U)AUE*)Ve@E3-dLn;)1j@I@Zxt9RhvtjNx~96lw!T*8(;siVzT z%rp36T|_^k-@f+8Ir<6xhIMklsi7>kSQ~A9h7a<-buY+0&b8vuArI?!!u|x7 z{8dZs{(yBJc^{Gs@=2@WICRLv{iA$%0_)r`R=YppK7+iuMcw)NU5z<($iw}sg7hWZ z(=}4NKjJ=zyuu+4+@I09V%H%L_s^|8$FM0St=jz=_gUnjAD}}X)=l&a#tHh{$LBd) zzku@|mmjdadM7PE)pk(v&9Ra(4frLEE2rPoXLY-21R@Wr?6 zUgIdu2YHA6XR_J9H_>#++nFQrb>GMFS5@-a#{*%nUx*NrWCixuhY=!iJY2YEqxgILs*798UR;||wFKJ*xe7*EK@I)VBk zAAZP3xv_4*5BXRx(C+XOO1yv2CH zctigoAAZQkIKX&>AI3ZS4?gfk9>xLY1AH*w(C_epFY+)BFdyNA`HudE4}6h_{>FL$ zAB-R5!w)*-Vcg(39?#Gcm z{Qy45JE!>aSpNnbI^^N~AhTZ`cBx;O_I`r*5#$BkEXlp6M{?+phxe0lN?~?nNd&J& z?;ZyxMQT3C3keS4rLwIYI^-Sxv#IszR?e^PIcz=HvmyWe3gfOh6*M2@t?pcnZ(UJE z(;+X@wk&+X;j(;j+4a^oBSQF{HCCQA#Ygi&p7*UPyydQ9nhtr5?-a1^9O=b-`^(l^ zmSP-wv>VzV`DlN%Bd+_u`6xHq4}NGr)ED{iLmt`<`N%{2As^o#;Q8yX)k(bANBvOWW{x&CzI#D!yrF)`pVwp( zdw;=`<9z~pF|ZrJ*7vN zulo+FBt_Ql(cy~}sdJZs!&1|FmX}(m3>?tE?;Xi9QL>biEMujJ{sX($`b^3R7}UQH z)r78*+NEo1-_$|F=pt0leq9C)OdT59UHW*QtkHRX(Xe3Kd_B*iB>OL@-udCrb+++Vpn51>dVh;lH zobP+s)36t9^YN1@T)gQIx3@*?At}dVqmN&-pT*^g?*r8-UoC`F`viD9d)X{DiwZM$ zoXz-HBs%g!dB6I(TlZrR3uK)a&;K9bvywId literal 0 HcmV?d00001 diff --git a/test/models-nonbsd/USD/usdc/texturedcube.usdc b/test/models-nonbsd/USD/usdc/texturedcube.usdc new file mode 100644 index 0000000000000000000000000000000000000000..899b005e3252ab30862cb407ea073fd30fcb9889 GIT binary patch literal 3551 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 JKY16$`!{4&2?ziH literal 0 HcmV?d00001 diff --git a/test/models-nonbsd/USD/usdc/textures/checkerboard.png b/test/models-nonbsd/USD/usdc/textures/checkerboard.png new file mode 100644 index 0000000000000000000000000000000000000000..fde967aca605b21710638f3049ffb43924cb094f GIT binary patch literal 7688 zcmeAS@N?(olHy`uVBq!ia0y~yUi(1B2{d zPZ!6KiaBp?9u#CU;9++3`ET2x;LIN{m5Yw8Dj{w_cBFZf%m zJkT75{|XI^3=AFu2Y_r9jszen#9{y>IhiCF7#dg@c^DWRni$v^7!({Cm>C!*j4B@u znbAZrnmtBKfNFtGfL<}Zo_GB+q30CIIWK;12FCg5zzfhJJwj0%s2$!LNY%?(5r1&oPDbidtQ Tws0n}6k+gm^>bP0l+XkKh(xEg literal 0 HcmV?d00001 diff --git a/test/models-nonbsd/USD/usdc/textures/texture-cat.jpg b/test/models-nonbsd/USD/usdc/textures/texture-cat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..22870c622e0871e051f47977a51dab3d9bec3c22 GIT binary patch literal 50234 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%0A*%-6<9 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 +#include +#include +#include + +using namespace ::Assimp; + +class utUSDImport : public AbstractImportExportBase { +public: + virtual bool importerTest() { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/../models-nonbsd/USD/suzanne.usdc", aiProcess_ValidateDataStructure); + EXPECT_EQ(1u, scene->mNumMeshes); + EXPECT_NE(nullptr, scene->mMeshes[0]); + if (nullptr == scene->mMeshes[0]) { + return false; + } + EXPECT_EQ(507u, scene->mMeshes[0]->mNumVertices); + EXPECT_EQ(968u, scene->mMeshes[0]->mNumFaces); + + return (nullptr != scene); + } +};