├── LICENSE └── 3dtiles ├── tools ├── CMakeLists.txt ├── 3dtiles-metadata.cpp ├── clone-3dtiles.cpp ├── b3dm2obj.cpp └── 3dtiles2obj.cpp ├── CMakeLists.txt ├── io.hpp ├── support.hpp ├── extensions └── mlwn │ ├── srs.cpp │ └── srs.hpp ├── support.cpp ├── reader.hpp ├── b3dm.hpp ├── encoder.hpp ├── reader.cpp ├── mesh.hpp ├── b3dm.cpp ├── encoder.cpp ├── 3dtiles.hpp ├── mesh.cpp └── 3dtiles.cpp /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Melown Technologies SE 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /3dtiles/tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | define_module(BINARY 3dtiles2obj 2 | DEPENDS geometry 3dtiles service imgproc 3 | ) 4 | 5 | set(3dtiles2obj_SOURCES 3dtiles2obj.cpp) 6 | add_executable(3dtiles2obj ${3dtiles2obj_SOURCES}) 7 | target_link_libraries(3dtiles2obj ${MODULE_LIBRARIES}) 8 | target_compile_definitions(3dtiles2obj PRIVATE ${MODULE_DEFINITIONS}) 9 | buildsys_binary(3dtiles2obj) 10 | 11 | define_module(BINARY clone-3dtiles 12 | DEPENDS geometry 3dtiles service imgproc 13 | ) 14 | 15 | set(clone-3dtiles_SOURCES clone-3dtiles.cpp) 16 | add_executable(clone-3dtiles ${clone-3dtiles_SOURCES}) 17 | target_link_libraries(clone-3dtiles ${MODULE_LIBRARIES}) 18 | target_compile_definitions(clone-3dtiles PRIVATE ${MODULE_DEFINITIONS}) 19 | buildsys_binary(clone-3dtiles) 20 | 21 | define_module(BINARY b3dm2obj 22 | DEPENDS geometry 3dtiles service imgproc 23 | ) 24 | 25 | set(b3dm2obj_SOURCES b3dm2obj.cpp) 26 | add_executable(b3dm2obj ${b3dm2obj_SOURCES}) 27 | target_link_libraries(b3dm2obj ${MODULE_LIBRARIES}) 28 | target_compile_definitions(b3dm2obj PRIVATE ${MODULE_DEFINITIONS}) 29 | buildsys_binary(b3dm2obj) 30 | 31 | define_module(BINARY 3dtiles-metadata 32 | DEPENDS 3dtiles service 33 | ) 34 | 35 | set(3dtiles-metadata_SOURCES 3dtiles-metadata.cpp) 36 | add_executable(3dtiles-metadata ${3dtiles-metadata_SOURCES}) 37 | target_link_libraries(3dtiles-metadata ${MODULE_LIBRARIES}) 38 | target_compile_definitions(3dtiles-metadata PRIVATE ${MODULE_DEFINITIONS}) 39 | buildsys_binary(3dtiles-metadata) 40 | -------------------------------------------------------------------------------- /3dtiles/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # bump version here 2 | set(3dtiles_VERSION 1.4) 3 | 4 | set(3dtiles_EXTRA_SOURCES) 5 | set(3dtiles_EXTRA_DEPENDS) 6 | if(MODULE_roarchive_FOUND) 7 | message(STATUS "3dtiles: compiling in roarchive support") 8 | list(APPEND 3dtiles_EXTRA_DEPENDS roarchive>=1.8) 9 | list(APPEND 3dtiles_EXTRA_SOURCES reader.hpp reader.cpp) 10 | else() 11 | message(STATUS "3dtiles: compiling without roarchive support") 12 | endif() 13 | 14 | # encoding support (needs VTS libraries) 15 | if(MODULE_vts-libs_FOUND) 16 | message(STATUS "3dtiles: compiling in encodig support (VTS)") 17 | list(APPEND 3dtiles_EXTRA_DEPENDS vts-libs) 18 | list(APPEND 3dtiles_EXTRA_SOURCES 19 | mesh.hpp mesh.cpp 20 | encoder.hpp encoder.cpp 21 | support.hpp support.cpp 22 | ) 23 | else() 24 | message(STATUS "3dtiles: compiling without encoding support (no VTS)") 25 | endif() 26 | 27 | define_module(LIBRARY 3dtiles=${3dtiles_VERSION} 28 | DEPENDS 29 | ${3dtiles_EXTRA_DEPENDS} 30 | jsoncpp>=2.3 gltf>=1.1 math>=1.4 utility>=1.41 dbglog>=1.4 imgproc>=1.28 31 | Boost_FILESYSTEM Boost_IOSTREAMS 32 | ) 33 | 34 | set(3dtiles_SOURCES 35 | 3dtiles.hpp 3dtiles.cpp 36 | b3dm.hpp b3dm.cpp 37 | 38 | extensions/mlwn/srs.hpp extensions/mlwn/srs.cpp 39 | 40 | ${3dtiles_EXTRA_SOURCES} 41 | ) 42 | 43 | add_library(3dtiles STATIC ${3dtiles_SOURCES}) 44 | buildsys_library(3dtiles) 45 | 46 | target_link_libraries(3dtiles ${MODULE_LIBRARIES}) 47 | target_compile_definitions(3dtiles PRIVATE ${MODULE_DEFINITIONS}) 48 | 49 | if(MODULE_service_FOUND) 50 | add_subdirectory(tools EXCLUDE_FROM_ALL) 51 | endif() 52 | -------------------------------------------------------------------------------- /3dtiles/io.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef tgreedtiles_io_hpp_included_ 28 | #define tgreedtiles_io_hpp_included_ 29 | 30 | #include 31 | 32 | #include "utility/streams.hpp" 33 | 34 | #include "3dtiles.hpp" 35 | 36 | namespace threedtiles { 37 | 38 | template 39 | inline std::basic_ostream& 40 | operator<<(std::basic_ostream &os, const TilePath &path) 41 | { 42 | return os << utility::join(path.path, "-", "root"); 43 | } 44 | 45 | } // namespace 3dtiles 46 | 47 | #endif // tgreedtiles_io_hpp_included_ 48 | -------------------------------------------------------------------------------- /3dtiles/support.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2021 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef threedtiles_support_hpp_included_ 28 | #define threedtiles_support_hpp_included_ 29 | 30 | #include 31 | #include 32 | 33 | #include "math/geometry_core.hpp" 34 | 35 | #include "geo/srsdef.hpp" 36 | 37 | namespace threedtiles { 38 | 39 | using Point3Convertor = std::function; 40 | using Point3Convertors = std::vector; 41 | 42 | Point3Convertor srs2Wgs84Rad(const geo::SrsDefinition &srs); 43 | 44 | } // namespace 3dtiles 45 | 46 | #endif // threedtiles_support_hpp_included_ 47 | -------------------------------------------------------------------------------- /3dtiles/extensions/mlwn/srs.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include "jsoncpp/as.hpp" 28 | 29 | #include "srs.hpp" 30 | 31 | namespace threedtiles { namespace extensions { namespace mlwn { 32 | 33 | // allocate space for this constant 34 | constexpr char Srs::extensionName[]; 35 | 36 | void build(Extension &ext, const Srs &srs) 37 | { 38 | Json::Value j(Json::objectValue); 39 | j["srs"] = srs.srs; 40 | ext = j; 41 | } 42 | 43 | void parse(Srs &srs, const Extension &ext) 44 | { 45 | const auto &j(boost::any_cast(ext)); 46 | Json::get(srs.srs, j); 47 | } 48 | 49 | } } } // namespace threedtiles::extensions::mlwn 50 | -------------------------------------------------------------------------------- /3dtiles/support.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2021 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include "support.hpp" 28 | 29 | #include "geo/csconvertor.hpp" 30 | 31 | namespace threedtiles { 32 | 33 | namespace { 34 | 35 | const geo::SrsDefinition wgs84(4979); 36 | 37 | inline double deg2rad(double d) { 38 | return d * M_PI / 180.; 39 | } 40 | 41 | } // namespace 42 | 43 | Point3Convertor srs2Wgs84Rad(const geo::SrsDefinition &srs) 44 | { 45 | geo::CsConvertor srs2wgs84(srs, wgs84); 46 | 47 | return [srs2wgs84=std::move(srs2wgs84)](const math::Point3 &p) { 48 | auto pp(srs2wgs84(p)); 49 | pp(0) = deg2rad(pp(0)); 50 | pp(1) = deg2rad(pp(1)); 51 | return pp; 52 | }; 53 | } 54 | 55 | } // namespace 3dtiles 56 | -------------------------------------------------------------------------------- /3dtiles/extensions/mlwn/srs.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef threedtiles_extensions_mlwn_srs_hpp_included_ 28 | #define threedtiles_extensions_mlwn_srs_hpp_included_ 29 | 30 | #include "../../3dtiles.hpp" 31 | 32 | namespace threedtiles { namespace extensions { namespace mlwn { 33 | 34 | /** MLWN_SRS extension 35 | */ 36 | struct Srs { 37 | static constexpr char extensionName[] = "MLWN_SRS"; 38 | 39 | /** Actual SRS value. 40 | */ 41 | std::string srs; 42 | }; 43 | 44 | void build(Extension &ext, const Srs &srs); 45 | 46 | void parse(Srs &srs, const Extension &ext); 47 | 48 | } } } // namespace threedtiles::extensions::mlwn 49 | 50 | #endif // threedtiles_extensions_mlwn_srs_hpp_included_ 51 | -------------------------------------------------------------------------------- /3dtiles/reader.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef threedtiles_reader_hpp_included_ 28 | #define threedtiles_reader_hpp_included_ 29 | 30 | #include "roarchive/roarchive.hpp" 31 | 32 | #include "gltf/meshloader.hpp" 33 | 34 | #include "3dtiles.hpp" 35 | 36 | namespace threedtiles { 37 | 38 | /** 3D Tiles archive reader 39 | */ 40 | class Archive { 41 | public: 42 | Archive(const boost::filesystem::path &root, const std::string &mime = "" 43 | , bool includeExternal = false); 44 | Archive(roarchive::RoArchive &archive, bool includeExternal = false); 45 | 46 | /** Generic I/O. 47 | */ 48 | roarchive::IStream::pointer 49 | istream(const boost::filesystem::path &path) const; 50 | 51 | /** Feed triangle mesh into parser from a b3dm file. 52 | */ 53 | void loadMesh(gltf::MeshLoader &loader 54 | , const boost::filesystem::path &path 55 | , const gltf::MeshLoader::DecodeOptions &options 56 | = gltf::MeshLoader::DecodeOptions()) const; 57 | 58 | /** Root tileset. 59 | */ 60 | const Tileset& tileset() const { return tileset_; } 61 | 62 | const boost::filesystem::path& tilesetPath() const { 63 | return tilesetPath_; 64 | } 65 | 66 | const boost::filesystem::path path(const boost::filesystem::path &path) 67 | const 68 | { 69 | return archive_.path(path); 70 | } 71 | 72 | bool remote() const; 73 | 74 | /** Number of tiles in root tileset. 75 | */ 76 | std::size_t treeSize() const { return treeSize_; } 77 | 78 | /** Read additional tileset file. 79 | */ 80 | Tileset tileset(const boost::filesystem::path &path 81 | , bool includeExternal = false 82 | , bool relaxed = false) const; 83 | 84 | private: 85 | const roarchive::RoArchive archive_; 86 | const boost::filesystem::path tilesetPath_; 87 | const Tileset tileset_; 88 | const std::size_t treeSize_; 89 | }; 90 | 91 | } // namespace threedtiles 92 | 93 | #endif // threedtiles_reader_hpp_included_ 94 | -------------------------------------------------------------------------------- /3dtiles/b3dm.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef threedtiles_b3dm_hpp_included_ 28 | #define threedtiles_b3dm_hpp_included_ 29 | 30 | #include 31 | 32 | #include "gltf/gltf.hpp" 33 | #include "gltf/meshloader.hpp" 34 | 35 | namespace threedtiles { 36 | 37 | struct BatchedModel { 38 | gltf::Model model; 39 | math::Point3 rtcCenter; 40 | }; 41 | 42 | /** Write a glTF archive as a b3dm file. 43 | */ 44 | void b3dm(std::ostream &os, const BatchedModel &model 45 | , const boost::filesystem::path &srcDir); 46 | 47 | /** Write a glTF archive as a b3dm file. 48 | */ 49 | void b3dm(const boost::filesystem::path &path, const BatchedModel &model 50 | , const boost::filesystem::path &srcDir); 51 | 52 | /** Write a glTF archive as a b3dm file. 53 | */ 54 | void b3dm(std::ostream &os, const gltf::Model &model 55 | , const boost::filesystem::path &srcDir 56 | , const math::Point3 &rtcCenter = math::Point3()); 57 | 58 | /** Write a glTF archive as a b3dm file. 59 | */ 60 | void b3dm(const boost::filesystem::path &path, const gltf::Model &model 61 | , const boost::filesystem::path &srcDir 62 | , const math::Point3 &rtcCenter = math::Point3()); 63 | 64 | /** Read a glTF archive from a b3dm file 65 | */ 66 | BatchedModel b3dm(std::istream &is 67 | , const boost::filesystem::path &path = "UNKNOWN"); 68 | 69 | /** Read a glTF archive from a b3dm file 70 | */ 71 | BatchedModel b3dm(const boost::filesystem::path &path); 72 | 73 | void loadMesh(gltf::MeshLoader &loader, const BatchedModel &model 74 | , gltf::MeshLoader::DecodeOptions options); 75 | 76 | // inlines 77 | 78 | inline void b3dm(std::ostream &os, const BatchedModel &model 79 | , const boost::filesystem::path &srcDir) 80 | { 81 | b3dm(os, model.model, srcDir, model.rtcCenter); 82 | } 83 | 84 | inline void b3dm(const boost::filesystem::path &path, const BatchedModel &model 85 | , const boost::filesystem::path &srcDir) 86 | { 87 | b3dm(path, model.model, srcDir, model.rtcCenter); 88 | } 89 | 90 | } // namespace threedtiles 91 | 92 | #endif // threedtiles_b3dm_hpp_included_ 93 | -------------------------------------------------------------------------------- /3dtiles/encoder.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef threedtiles_encoder_hpp_included_ 28 | #define threedtiles_encoder_hpp_included_ 29 | 30 | #include 31 | 32 | #include 33 | 34 | #include "utility/gccversion.hpp" 35 | 36 | #include "vts-libs/vts/tileindex.hpp" 37 | 38 | #include "3dtiles.hpp" 39 | #include "mesh.hpp" 40 | 41 | namespace vts = vtslibs::vts; 42 | 43 | namespace threedtiles { 44 | 45 | class Encoder { 46 | public: 47 | struct Config { 48 | geo::SrsDefinition srs; 49 | double geometricErrorFactor; 50 | std::size_t tilesetLimit; 51 | bool parallel; 52 | 53 | Config() 54 | : srs(4328) 55 | , geometricErrorFactor(16.0) 56 | , tilesetLimit(1000) 57 | , parallel(true) 58 | {} 59 | 60 | void configuration(boost::program_options::options_description 61 | &config); 62 | 63 | void configure(const boost::program_options::variables_map &vars); 64 | }; 65 | 66 | Encoder(const Config &config, const boost::filesystem::path &output 67 | , const vts::TileIndex &validTiles) 68 | : config_(config), output_(output), validTiles_(validTiles) 69 | {} 70 | 71 | virtual ~Encoder() {} 72 | 73 | void run(); 74 | 75 | struct TexturedMesh { 76 | vts::Mesh::pointer mesh; 77 | vts::Atlas::pointer atlas; 78 | 79 | template 80 | AtlasType& initialize() { 81 | mesh = std::make_shared(); 82 | auto atlas(std::make_shared()); 83 | this->atlas = atlas; 84 | return *atlas; 85 | } 86 | }; 87 | 88 | class Detail; 89 | friend class Detail; 90 | 91 | protected: 92 | const Config& config() const { return config_; } 93 | 94 | private: 95 | virtual TexturedMesh generate(const vts::TileId &tileId) = 0; 96 | 97 | const Config &config_; 98 | const boost::filesystem::path output_; 99 | 100 | vts::TileIndex validTiles_; 101 | 102 | Tileset tileset_; 103 | }; 104 | 105 | } // namespace threedtiles 106 | 107 | #endif // threedtiles_encoder_hpp_included_ 108 | -------------------------------------------------------------------------------- /3dtiles/tools/3dtiles-metadata.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2021 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVaENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include 28 | 29 | #include 30 | #include 31 | 32 | #include "utility/buildsys.hpp" 33 | #include "utility/gccversion.hpp" 34 | #include "utility/limits.hpp" 35 | #include "utility/path.hpp" 36 | #include "utility/openmp.hpp" 37 | #include "utility/format.hpp" 38 | #include "utility/streams.hpp" 39 | 40 | #include "service/cmdline.hpp" 41 | 42 | #include "3dtiles/reader.hpp" 43 | 44 | namespace po = boost::program_options; 45 | namespace bio = boost::iostreams; 46 | namespace fs = boost::filesystem; 47 | namespace tdt = threedtiles; 48 | 49 | namespace { 50 | 51 | class Metadata : public service::Cmdline 52 | { 53 | public: 54 | Metadata() 55 | : service::Cmdline("3dtiles-metadata", BUILD_TARGET_VERSION) 56 | {} 57 | 58 | private: 59 | virtual void configuration(po::options_description &cmdline 60 | , po::options_description &config 61 | , po::positional_options_description &pd) 62 | override; 63 | 64 | virtual void configure(const po::variables_map &vars) 65 | override; 66 | 67 | virtual bool help(std::ostream &out, const std::string &what) const 68 | override; 69 | 70 | virtual int run() override; 71 | 72 | fs::path input_; 73 | bool includeExternal_ = true; 74 | }; 75 | 76 | void Metadata::configuration(po::options_description &cmdline 77 | , po::options_description &config 78 | , po::positional_options_description &pd) 79 | { 80 | cmdline.add_options() 81 | ("input", po::value(&input_)->required() 82 | , "Path to input SLPK archive.") 83 | ("includeExternal", po::value(&includeExternal_) 84 | ->default_value(includeExternal_) 85 | , "Read external references into one metadata tree.") 86 | ; 87 | 88 | pd 89 | .add("input", 1) 90 | ; 91 | 92 | (void) config; 93 | } 94 | 95 | void Metadata::configure(const po::variables_map&) {} 96 | 97 | bool Metadata::help(std::ostream &out, const std::string &what) const 98 | { 99 | if (what.empty()) { 100 | out << R"RAW(3dtils-metadata 101 | 102 | Dumps 3D Tiles metadata in more readable way. 103 | 104 | usage 105 | 3dtiles-metadata INPUT [OPTIONS] 106 | )RAW"; 107 | } 108 | return false; 109 | } 110 | 111 | int Metadata::run() 112 | { 113 | tdt::Archive ar(input_, {}, includeExternal_); 114 | 115 | const auto &ts(ar.tileset()); 116 | 117 | std::cout << std::fixed << std::setprecision(10); 118 | dumpMetadata(std::cout, ts); 119 | std::cout << std::flush; 120 | 121 | return EXIT_SUCCESS; 122 | } 123 | 124 | } // namespace 125 | 126 | int main(int argc, char *argv[]) 127 | { 128 | utility::unlimitedCoredump(); 129 | return Metadata()(argc, argv); 130 | } 131 | -------------------------------------------------------------------------------- /3dtiles/reader.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include 28 | 29 | #include "dbglog/dbglog.hpp" 30 | 31 | #include "utility/uri.hpp" 32 | #include "utility/openmp.hpp" 33 | 34 | #include "reader.hpp" 35 | #include "b3dm.hpp" 36 | 37 | namespace fs = boost::filesystem; 38 | namespace ba = boost::algorithm; 39 | 40 | namespace threedtiles { 41 | 42 | namespace { 43 | 44 | namespace constants { 45 | const std::string TilesetJson("tileset.json"); 46 | } // namespace constants 47 | 48 | inline const fs::path& coalesce(const boost::optional &opt 49 | , const fs::path &dflt) 50 | { 51 | return opt ? *opt : dflt; 52 | } 53 | 54 | } // namespace 55 | 56 | Archive::Archive(const fs::path &root, const std::string &mime 57 | , bool includeExternal) 58 | : archive_(root 59 | , roarchive::OpenOptions().setHint(constants::TilesetJson) 60 | .setInlineHint('#') 61 | .setMime(mime)) 62 | , tilesetPath_(coalesce(archive_.usedHint(), constants::TilesetJson)) 63 | , tileset_(tileset(tilesetPath_, includeExternal)) 64 | , treeSize_(tileset_.root->subtreeSize()) 65 | {} 66 | 67 | Archive::Archive(roarchive::RoArchive &archive, bool includeExternal) 68 | : archive_(archive.applyHint(constants::TilesetJson)) 69 | , tilesetPath_(coalesce(archive_.usedHint(), constants::TilesetJson)) 70 | , tileset_(tileset(tilesetPath_, includeExternal)) 71 | , treeSize_(tileset_.root->subtreeSize()) 72 | {} 73 | 74 | roarchive::IStream::pointer Archive::istream(const fs::path &path) const 75 | { 76 | // TODO: check for remote URL and fail if archive is non-remote 77 | return archive_.istream(path); 78 | } 79 | 80 | namespace { 81 | 82 | void include(const Archive &archive, Tile::pointer &root) 83 | { 84 | if (!root->children.empty()) { 85 | // non-empty children 86 | for (auto &child : root->children) { 87 | include(archive, child); 88 | } 89 | return; 90 | } 91 | 92 | if (!root->content) { return; } 93 | 94 | // some content, recurse to external tileset 95 | if (ba::iends_with(root->content->uri, ".json")) { 96 | #ifndef _MSC_VER // omp task is not supported by msvc 97 | UTILITY_OMP(task shared(archive, root)) 98 | #endif 99 | { 100 | root = archive.tileset(root->content->uri, false, true).root; 101 | include(archive, root); 102 | } 103 | } 104 | } 105 | 106 | } // namespace 107 | 108 | Tileset Archive::tileset(const fs::path &path, bool includeExternal 109 | , bool relaxed) const 110 | { 111 | Tileset ts; 112 | if (!includeExternal) { 113 | read(*istream(path), ts, path); 114 | absolutize(ts, path.string(), relaxed); 115 | return ts; 116 | } 117 | 118 | UTILITY_OMP(parallel shared(ts, path)) 119 | { 120 | UTILITY_OMP(single) 121 | { 122 | read(*istream(path), ts, path); 123 | absolutize(ts, path.string(), relaxed); 124 | include(*this, ts.root); 125 | } 126 | } 127 | 128 | return ts; 129 | } 130 | 131 | void Archive::loadMesh(gltf::MeshLoader &loader 132 | , const boost::filesystem::path &path 133 | , const gltf::MeshLoader::DecodeOptions &options) const 134 | { 135 | threedtiles::loadMesh(loader, b3dm(*istream(path), path), options); 136 | } 137 | 138 | bool Archive::remote() const 139 | { 140 | return archive_.handlesSchema("http") || archive_.handlesSchema("https"); 141 | } 142 | 143 | } // namespace threedtiles 144 | -------------------------------------------------------------------------------- /3dtiles/mesh.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef threedtiles_mesh_hpp_included_ 28 | #define threedtiles_mesh_hpp_included_ 29 | 30 | #include 31 | #include 32 | 33 | #include "vts-libs/vts/basetypes3.hpp" 34 | #include "vts-libs/vts/mesh.hpp" 35 | #include "vts-libs/vts/atlas.hpp" 36 | 37 | #include "gltf/gltf.hpp" 38 | 39 | namespace threedtiles { 40 | 41 | /** Image source. 42 | */ 43 | class ImageSource { 44 | public: 45 | virtual ~ImageSource() {} 46 | virtual gltf::Image image(int idx) const = 0; 47 | virtual std::string info(int idx) const = 0; 48 | }; 49 | 50 | /** Image source-based savers. 51 | */ 52 | void saveTile(std::ostream &os, const boost::filesystem::path &path 53 | , const vtslibs::vts::TileId &tileId 54 | , const vtslibs::vts::ConstSubMeshRange &submeshes 55 | , const ImageSource &images); 56 | 57 | /** Image source-based savers. 58 | */ 59 | boost::filesystem::path 60 | saveTile(const boost::filesystem::path &root 61 | , const vtslibs::vts::TileId &tileId 62 | , const vtslibs::vts::ConstSubMeshRange &submeshes 63 | , const ImageSource &images 64 | , const boost::optional 65 | &z = boost::none); 66 | 67 | /** Atlas based savers. 68 | */ 69 | boost::filesystem::path 70 | saveTile(const boost::filesystem::path &root 71 | , const vtslibs::vts::TileId &tileId 72 | , const vtslibs::vts::Mesh &mesh 73 | , const vtslibs::vts::Atlas &atlas 74 | , const boost::optional 75 | &z = boost::none); 76 | 77 | boost::filesystem::path 78 | saveTile(const boost::filesystem::path &root 79 | , const vtslibs::vts::TileId &tileId 80 | , const vtslibs::vts::ConstSubMeshRange &submeshes 81 | , const vtslibs::vts::Atlas &atlas 82 | , const boost::optional 83 | &z = boost::none); 84 | 85 | boost::filesystem::path saveTile(const boost::filesystem::path &root 86 | , const vtslibs::vts::TileId3 &tileId 87 | , const vtslibs::vts::Mesh &mesh 88 | , const vtslibs::vts::Atlas &atlas); 89 | 90 | boost::filesystem::path 91 | saveTile(const boost::filesystem::path &root 92 | , const vtslibs::vts::TileId3 &tileId 93 | , const vtslibs::vts::ConstSubMeshRange &submeshes 94 | , const vtslibs::vts::Atlas &atlas); 95 | 96 | 97 | boost::filesystem::path 98 | tilePath(const vtslibs::vts::TileId &tileId 99 | , const std::string &extension 100 | , const boost::optional 101 | &z = boost::none); 102 | 103 | boost::filesystem::path tilePath(const vtslibs::vts::TileId3 &tileId 104 | , const std::string &extension); 105 | 106 | // inlines 107 | 108 | /** Atlas image source. 109 | */ 110 | namespace detail { 111 | 112 | class AtlasSource : public ImageSource { 113 | public: 114 | AtlasSource(const vtslibs::vts::Atlas &atlas) 115 | : atlas_(atlas) 116 | {} 117 | 118 | gltf::Image image(int idx) const override; 119 | std::string info(int idx) const override; 120 | 121 | private: 122 | const vtslibs::vts::Atlas &atlas_; 123 | }; 124 | 125 | } // namespace detail 126 | 127 | inline boost::filesystem::path 128 | saveTile(const boost::filesystem::path &root 129 | , const vtslibs::vts::TileId &tileId 130 | , const vtslibs::vts::Mesh &mesh 131 | , const vtslibs::vts::Atlas &atlas 132 | , const boost::optional &z) 133 | { 134 | return saveTile(root, tileId, vtslibs::vts::submeshRange(mesh) 135 | , detail::AtlasSource(atlas), z); 136 | } 137 | 138 | inline boost::filesystem::path 139 | saveTile(const boost::filesystem::path &root 140 | , const vtslibs::vts::TileId &tileId 141 | , const vtslibs::vts::ConstSubMeshRange &submeshes 142 | , const vtslibs::vts::Atlas &atlas 143 | , const boost::optional &z) 144 | { 145 | return saveTile(root, tileId, submeshes, detail::AtlasSource(atlas), z); 146 | } 147 | 148 | inline boost::filesystem::path saveTile(const boost::filesystem::path &root 149 | , const vtslibs::vts::TileId3 &tileId 150 | , const vtslibs::vts::Mesh &mesh 151 | , const vtslibs::vts::Atlas &atlas) 152 | { 153 | return saveTile(root, tileId.tileId(), vtslibs::vts::submeshRange(mesh) 154 | , detail::AtlasSource(atlas), tileId.z); 155 | } 156 | 157 | inline boost::filesystem::path 158 | saveTile(const boost::filesystem::path &root 159 | , const vtslibs::vts::TileId3 &tileId 160 | , const vtslibs::vts::ConstSubMeshRange &submeshes 161 | , const vtslibs::vts::Atlas &atlas) 162 | { 163 | return saveTile(root, tileId.tileId(), submeshes 164 | , detail::AtlasSource(atlas), tileId.z); 165 | } 166 | 167 | inline boost::filesystem::path tilePath(const vtslibs::vts::TileId3 &tileId 168 | , const std::string &extension) 169 | { 170 | return tilePath(tileId.tileId(), extension, tileId.z); 171 | } 172 | 173 | } // namespace threedtiles 174 | 175 | #endif // threedtiles_mesh_hpp_included_ 176 | -------------------------------------------------------------------------------- /3dtiles/tools/clone-3dtiles.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | #include "utility/buildsys.hpp" 34 | #include "utility/gccversion.hpp" 35 | #include "utility/limits.hpp" 36 | #include "utility/path.hpp" 37 | #include "utility/openmp.hpp" 38 | #include "utility/format.hpp" 39 | #include "utility/streams.hpp" 40 | #include "utility/uri.hpp" 41 | 42 | #include "service/cmdline.hpp" 43 | 44 | #include "geometry/meshop.hpp" 45 | 46 | #include "imgproc/imagesize.cpp" 47 | 48 | #include "3dtiles/reader.hpp" 49 | 50 | namespace po = boost::program_options; 51 | namespace bio = boost::iostreams; 52 | namespace fs = boost::filesystem; 53 | namespace ba = boost::algorithm; 54 | namespace tdt = threedtiles; 55 | 56 | namespace { 57 | 58 | class Clone3dTiles : public service::Cmdline 59 | { 60 | public: 61 | Clone3dTiles() 62 | : service::Cmdline("clone-3dtiles", BUILD_TARGET_VERSION) 63 | {} 64 | 65 | private: 66 | virtual void configuration(po::options_description &cmdline 67 | , po::options_description &config 68 | , po::positional_options_description &pd) 69 | override; 70 | 71 | virtual void configure(const po::variables_map &vars) 72 | override; 73 | 74 | virtual bool help(std::ostream &out, const std::string &what) const 75 | override; 76 | 77 | virtual int run() override; 78 | 79 | void process(const tdt::Archive &archive, tdt::Tileset &&ts 80 | , std::string &tsPath, const fs::path &parentTsPath); 81 | 82 | fs::path output_; 83 | fs::path input_; 84 | }; 85 | 86 | void Clone3dTiles::configuration(po::options_description &cmdline 87 | , po::options_description &config 88 | , po::positional_options_description &pd) 89 | { 90 | cmdline.add_options() 91 | ("output", po::value(&output_)->required() 92 | , "Path to output converted input.") 93 | ("input", po::value(&input_)->required() 94 | , "Path to input SLPK archive.") 95 | ; 96 | 97 | pd 98 | .add("input", 1) 99 | .add("output", 1); 100 | 101 | (void) config; 102 | } 103 | 104 | void Clone3dTiles::configure(const po::variables_map&) {} 105 | 106 | bool Clone3dTiles::help(std::ostream &out, const std::string &what) const 107 | { 108 | if (what.empty()) { 109 | out << R"RAW(clone-3dtiles 110 | 111 | Clones 3D TIles archive into local disk. 112 | 113 | usage 114 | clone-3dtiles INPUT OUTPUT [OPTIONS] 115 | )RAW"; 116 | } 117 | return false; 118 | } 119 | 120 | bool isJson(const std::string &uri) 121 | { 122 | return ba::iends_with(uri, ".json"); 123 | } 124 | 125 | fs::path uri2path(const utility::Uri &uri) 126 | { 127 | if (uri.scheme().empty()) { return str(uri); } 128 | 129 | fs::path p; 130 | std::ostringstream os; 131 | os << uri.scheme() << '_' << uri.components().host; 132 | if (uri.components().port > -1) { 133 | os << '_' << uri.components().port; 134 | } 135 | return p / os.str() / uri.path(); 136 | } 137 | 138 | void Clone3dTiles::process(const tdt::Archive &archive, tdt::Tileset &&ts 139 | , std::string &tsPath, const fs::path &parentTsPath) 140 | { 141 | LOG(info3) << "Processing tileset <" << tsPath << ">."; 142 | 143 | fs::path tsOutputPath(uri2path(tsPath)); 144 | 145 | traverse(*ts.root, [&](tdt::Tile &tile, const tdt::TilePath&) 146 | -> void 147 | { 148 | if (!tile.content) { return; } 149 | 150 | if (isJson(tile.content->uri)) { 151 | return process(archive, archive.tileset(tile.content->uri) 152 | , tile.content->uri, tsOutputPath); 153 | } 154 | 155 | fs::path outputPath(uri2path(tile.content->uri)); 156 | 157 | LOG(info3) << "Saving <" << tile.content->uri << "> in " 158 | << outputPath << "."; 159 | 160 | // load and save 161 | { 162 | const auto path(output_ / outputPath); 163 | fs::create_directories(path.parent_path()); 164 | copy(archive.istream(tile.content->uri), path); 165 | } 166 | 167 | // replace path (make relative) 168 | tile.content->uri 169 | = utility::lexically_relative 170 | (outputPath, tsOutputPath.parent_path()).string(); 171 | }); 172 | 173 | // build path and store 174 | LOG(info3) << "Saving <" << tsPath << "> in " << tsOutputPath << "."; 175 | { 176 | const auto path(output_ / tsOutputPath); 177 | fs::create_directories(path.parent_path()); 178 | write(path, ts); 179 | } 180 | 181 | // replace path 182 | LOG(info4) << "tsOutputPath: " << tsOutputPath; 183 | LOG(info4) << "parentTsPath: " << parentTsPath; 184 | LOG(info4) << "parentTsPath.parent_path(): " << parentTsPath.parent_path(); 185 | tsPath = utility::lexically_relative 186 | (tsOutputPath, parentTsPath.parent_path()).string(); 187 | LOG(info4) << "tsPath: " << tsPath; 188 | } 189 | 190 | int Clone3dTiles::run() 191 | { 192 | tdt::Archive archive(input_); 193 | 194 | auto tsPath((archive.remote() ? archive.path(archive.tilesetPath()) 195 | : archive.tilesetPath()).string()); 196 | process(archive, clone(archive.tileset()), tsPath, {}); 197 | 198 | return EXIT_SUCCESS; 199 | } 200 | 201 | } // namespace 202 | 203 | int main(int argc, char *argv[]) 204 | { 205 | utility::unlimitedCoredump(); 206 | return Clone3dTiles()(argc, argv); 207 | } 208 | -------------------------------------------------------------------------------- /3dtiles/tools/b3dm2obj.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVaENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include 28 | 29 | #include 30 | #include 31 | 32 | #include "utility/buildsys.hpp" 33 | #include "utility/gccversion.hpp" 34 | #include "utility/limits.hpp" 35 | #include "utility/path.hpp" 36 | #include "utility/openmp.hpp" 37 | #include "utility/format.hpp" 38 | #include "utility/streams.hpp" 39 | 40 | #include "service/cmdline.hpp" 41 | 42 | #include "geometry/meshop.hpp" 43 | 44 | #include "imgproc/imagesize.cpp" 45 | 46 | #include "3dtiles/b3dm.hpp" 47 | #include "gltf/meshloader.hpp" 48 | 49 | namespace po = boost::program_options; 50 | namespace bio = boost::iostreams; 51 | namespace fs = boost::filesystem; 52 | namespace tdt = threedtiles; 53 | 54 | namespace { 55 | 56 | class B3dm2Obj : public service::Cmdline 57 | { 58 | public: 59 | B3dm2Obj() 60 | : service::Cmdline("b3dm2obj", BUILD_TARGET_VERSION) 61 | {} 62 | 63 | private: 64 | virtual void configuration(po::options_description &cmdline 65 | , po::options_description &config 66 | , po::positional_options_description &pd) 67 | override; 68 | 69 | virtual void configure(const po::variables_map &vars) 70 | override; 71 | 72 | virtual bool help(std::ostream &out, const std::string &what) const 73 | override; 74 | 75 | virtual int run() override; 76 | 77 | fs::path output_; 78 | fs::path input_; 79 | }; 80 | 81 | void B3dm2Obj::configuration(po::options_description &cmdline 82 | , po::options_description &config 83 | , po::positional_options_description &pd) 84 | { 85 | cmdline.add_options() 86 | ("output", po::value(&output_)->required() 87 | , "Path to output converted input.") 88 | ("input", po::value(&input_)->required() 89 | , "Path to input SLPK archive.") 90 | ; 91 | 92 | pd 93 | .add("input", 1) 94 | .add("output", 1); 95 | 96 | (void) config; 97 | } 98 | 99 | void B3dm2Obj::configure(const po::variables_map&) {} 100 | 101 | bool B3dm2Obj::help(std::ostream &out, const std::string &what) const 102 | { 103 | if (what.empty()) { 104 | out << R"RAW(b3dm2obj 105 | 106 | Converts batched 3D model into textured meshes in OBJ format. 107 | 108 | usage 109 | b3dm2obj INPUT OUTPUT [OPTIONS] 110 | )RAW"; 111 | } 112 | return false; 113 | } 114 | 115 | struct MeshLoader : gltf::MeshLoader 116 | { 117 | MeshLoader(const fs::path &output) 118 | : output(output), smIndex(-1), smStart() 119 | {} 120 | 121 | /** New mesh has been encountered. 122 | */ 123 | void mesh() override { 124 | smStart = m.vertices.size(); 125 | ++smIndex; 126 | } 127 | 128 | /** Mesh vertices. 129 | */ 130 | void vertices(math::Points3d &&v) override { 131 | m.vertices.insert(m.vertices.end() 132 | , std::make_move_iterator(v.begin()) 133 | , std::make_move_iterator(v.end())); 134 | } 135 | 136 | /** Mesh texture coordinates. 137 | */ 138 | void tc(math::Points2d &&tc) override { 139 | m.tCoords.insert(m.tCoords.end() 140 | , std::make_move_iterator(tc.begin()) 141 | , std::make_move_iterator(tc.end())); 142 | } 143 | 144 | /** Mexh faces. Indices are valid for both 3D and 2D vertices (i.e. vertices 145 | * and texture coordinates. 146 | */ 147 | void faces(Faces &&faces) override { 148 | for (const auto &face : faces) { 149 | m.faces.emplace_back 150 | (face(0) + smStart, face(1) + smStart, face(2) + smStart 151 | , face(0) + smStart, face(1) + smStart, face(2) + smStart 152 | , smIndex); 153 | } 154 | } 155 | 156 | /** Image data. 157 | */ 158 | void image(const DataView &imageData) override { 159 | const auto &type(imgproc::imageType 160 | (imageData.first, gltf::size(imageData))); 161 | 162 | const auto name(utility::format("%d%s", smIndex, type)); 163 | utility::write(output / name, imageData.first, gltf::size(imageData)); 164 | imageNames.push_back(name); 165 | } 166 | 167 | /** Image reference. 168 | */ 169 | void image(const fs::path &path) override { 170 | imageNames.push_back(path.string()); 171 | } 172 | 173 | void finish() { 174 | { 175 | LOG(info1) << "Writing material file."; 176 | std::ofstream f((output / "mesh.mtl").string()); 177 | 178 | int index(0); 179 | for (const auto &name : imageNames) { 180 | f << "newmtl " << index << "\n" 181 | << "map_Kd " << name 182 | << "\n"; 183 | 184 | ++index; 185 | } 186 | f.close(); 187 | } 188 | 189 | LOG(info3) << "Saving mesh to " << output << "."; 190 | saveAsObj(m, output / "mesh.obj", geometry::ObjMaterial("mesh.mtl")); 191 | } 192 | 193 | const fs::path output; 194 | geometry::Mesh m; 195 | int smIndex; 196 | geometry::Face::index_type smStart; 197 | std::vector imageNames; 198 | }; 199 | 200 | int B3dm2Obj::run() 201 | { 202 | auto model(tdt::b3dm(input_)); 203 | 204 | fs::create_directories(output_); 205 | MeshLoader loader(output_); 206 | MeshLoader::DecodeOptions options; 207 | options.flipTc = true; 208 | tdt::loadMesh(loader, model, options); 209 | loader.finish(); 210 | 211 | return EXIT_SUCCESS; 212 | } 213 | 214 | } // namespace 215 | 216 | int main(int argc, char *argv[]) 217 | { 218 | utility::unlimitedCoredump(); 219 | return B3dm2Obj()(argc, argv); 220 | } 221 | -------------------------------------------------------------------------------- /3dtiles/tools/3dtiles2obj.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include 28 | 29 | #include 30 | #include 31 | 32 | #include "utility/buildsys.hpp" 33 | #include "utility/gccversion.hpp" 34 | #include "utility/limits.hpp" 35 | #include "utility/path.hpp" 36 | #include "utility/openmp.hpp" 37 | #include "utility/format.hpp" 38 | #include "utility/streams.hpp" 39 | 40 | #include "service/cmdline.hpp" 41 | 42 | #include "geometry/meshop.hpp" 43 | 44 | #include "imgproc/imagesize.cpp" 45 | 46 | #include "3dtiles/reader.hpp" 47 | 48 | namespace po = boost::program_options; 49 | namespace bio = boost::iostreams; 50 | namespace fs = boost::filesystem; 51 | namespace tdt = threedtiles; 52 | 53 | namespace { 54 | 55 | class TDTiles2Obj : public service::Cmdline 56 | { 57 | public: 58 | TDTiles2Obj() 59 | : service::Cmdline("3dtiles2obj", BUILD_TARGET_VERSION) 60 | {} 61 | 62 | private: 63 | virtual void configuration(po::options_description &cmdline 64 | , po::options_description &config 65 | , po::positional_options_description &pd) 66 | override; 67 | 68 | virtual void configure(const po::variables_map &vars) 69 | override; 70 | 71 | virtual bool help(std::ostream &out, const std::string &what) const 72 | override; 73 | 74 | virtual int run() override; 75 | 76 | fs::path output_; 77 | fs::path input_; 78 | }; 79 | 80 | void TDTiles2Obj::configuration(po::options_description &cmdline 81 | , po::options_description &config 82 | , po::positional_options_description &pd) 83 | { 84 | cmdline.add_options() 85 | ("output", po::value(&output_)->required() 86 | , "Path to output converted input.") 87 | ("input", po::value(&input_)->required() 88 | , "Path to input SLPK archive.") 89 | ; 90 | 91 | pd 92 | .add("input", 1) 93 | .add("output", 1); 94 | 95 | (void) config; 96 | } 97 | 98 | void TDTiles2Obj::configure(const po::variables_map&) {} 99 | 100 | bool TDTiles2Obj::help(std::ostream &out, const std::string &what) const 101 | { 102 | if (what.empty()) { 103 | out << R"RAW(3dtiles2obj 104 | 105 | Converts 3D TIles archive into textured meshes in OBJ format. 106 | 107 | usage 108 | 3dtiles2obj INPUT OUTPUT [OPTIONS] 109 | )RAW"; 110 | } 111 | return false; 112 | } 113 | 114 | struct MeshLoader : gltf::MeshLoader 115 | { 116 | MeshLoader(const fs::path &output) 117 | : output(output), smIndex(-1), smStart() 118 | {} 119 | 120 | /** New mesh has been encountered. 121 | */ 122 | void mesh() override { 123 | smStart = m.vertices.size(); 124 | ++smIndex; 125 | } 126 | 127 | /** Mesh vertices. 128 | */ 129 | void vertices(math::Points3d &&v) override { 130 | m.vertices.insert(m.vertices.end() 131 | , std::make_move_iterator(v.begin()) 132 | , std::make_move_iterator(v.end())); 133 | } 134 | 135 | /** Mesh texture coordinates. 136 | */ 137 | void tc(math::Points2d &&tc) override { 138 | m.tCoords.insert(m.tCoords.end() 139 | , std::make_move_iterator(tc.begin()) 140 | , std::make_move_iterator(tc.end())); 141 | } 142 | 143 | /** Mexh faces. Indices are valid for both 3D and 2D vertices (i.e. vertices 144 | * and texture coordinates. 145 | */ 146 | void faces(Faces &&faces) override { 147 | for (const auto &face : faces) { 148 | m.faces.emplace_back 149 | (face(0) + smStart, face(1) + smStart, face(2) + smStart 150 | , face(0) + smStart, face(1) + smStart, face(2) + smStart 151 | , smIndex); 152 | } 153 | } 154 | 155 | /** Image data. 156 | */ 157 | void image(const DataView &imageData) override { 158 | const auto &type(imgproc::imageType 159 | (imageData.first, gltf::size(imageData))); 160 | 161 | const auto name(utility::format("%d%s", smIndex, type)); 162 | utility::write(output / name, imageData.first, gltf::size(imageData)); 163 | imageNames.push_back(name); 164 | } 165 | 166 | /** Image data. 167 | */ 168 | void image(const fs::path &path) override { 169 | imageNames.push_back(path); 170 | } 171 | 172 | void finish() { 173 | { 174 | LOG(info1) << "Writing material file."; 175 | std::ofstream f((output / "mesh.mtl").string()); 176 | 177 | int index(0); 178 | for (const auto &name : imageNames) { 179 | f << "newmtl " << index << "\n" 180 | << "map_Kd " << name 181 | << "\n"; 182 | 183 | ++index; 184 | } 185 | f.close(); 186 | } 187 | 188 | LOG(info3) << "Saving mesh to " << output << "."; 189 | saveAsObj(m, output / "mesh.obj", geometry::ObjMaterial("mesh.mtl")); 190 | } 191 | 192 | const fs::path output; 193 | geometry::Mesh m; 194 | int smIndex; 195 | geometry::Face::index_type smStart; 196 | std::vector imageNames; 197 | }; 198 | 199 | int TDTiles2Obj::run() 200 | { 201 | tdt::Archive archive(input_, "", true); 202 | 203 | ctraverse(*archive.tileset().root 204 | , [&](const tdt::Tile &tile, const tdt::TilePath &path) 205 | { 206 | if (!tile.content) { return; } 207 | 208 | const auto dir 209 | (output_ / boost::lexical_cast 210 | (utility::join(path.path, "/"))); 211 | fs::create_directories(dir); 212 | 213 | MeshLoader loader(dir); 214 | MeshLoader::DecodeOptions options; 215 | options.trafo = *tile.transform; 216 | options.flipTc = true; 217 | archive.loadMesh(loader, tile.content->uri, options); 218 | 219 | loader.finish(); 220 | }); 221 | 222 | 223 | return EXIT_SUCCESS; 224 | } 225 | 226 | } // namespace 227 | 228 | int main(int argc, char *argv[]) 229 | { 230 | utility::unlimitedCoredump(); 231 | return TDTiles2Obj()(argc, argv); 232 | } 233 | -------------------------------------------------------------------------------- /3dtiles/b3dm.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | 33 | #include "dbglog/dbglog.hpp" 34 | 35 | #include "utility/streams.hpp" 36 | #include "utility/binaryio.hpp" 37 | 38 | #include "jsoncpp/io.hpp" 39 | 40 | #include "gltf/meshloader.hpp" 41 | 42 | #include "b3dm.hpp" 43 | 44 | namespace bin = utility::binaryio; 45 | namespace bio = boost::iostreams; 46 | 47 | namespace threedtiles { 48 | 49 | namespace detail { 50 | 51 | const char MAGIC[4] = { 'b', '3', 'd', 'm' }; 52 | 53 | struct Header { 54 | std::uint32_t version = 1; 55 | std::uint32_t byteLength = size(); 56 | std::uint32_t featureTableJSONByteLength = 0; 57 | std::uint32_t featureTableBinaryByteLength = 0; 58 | std::uint32_t batchTableJSONByteLength = 0; 59 | std::uint32_t batchTableBinaryByteLength = 0; 60 | 61 | static std::size_t size() { 62 | return 7 * sizeof(std::uint32_t); 63 | } 64 | }; 65 | 66 | void write(std::ostream &os, const Header &header) 67 | { 68 | bin::write(os, MAGIC); 69 | bin::write(os, header.version); 70 | bin::write(os, header.byteLength); 71 | bin::write(os, header.featureTableJSONByteLength); 72 | bin::write(os, header.featureTableBinaryByteLength); 73 | bin::write(os, header.batchTableJSONByteLength); 74 | bin::write(os, header.batchTableBinaryByteLength); 75 | } 76 | 77 | template 78 | void pad(std::ostream &os, std::size_t offset = 0, char chr = ' ') 79 | { 80 | offset += os.tellp(); 81 | if (const auto used = (offset % alignment)) { 82 | const auto padding(alignment - used); 83 | std::fill_n(std::ostream_iterator(os), padding, chr); 84 | } 85 | } 86 | 87 | void featureTable(std::ostream &os 88 | , const math::Point3 &rtcCenter 89 | , std::size_t offset = 0) 90 | { 91 | Json::Value ft(Json::objectValue); 92 | ft["BATCH_LENGTH"] = 0; 93 | if (rtcCenter != math::Point3()) { 94 | auto &c(ft["RTC_CENTER"] = Json::arrayValue); 95 | c.append(rtcCenter(0)); 96 | c.append(rtcCenter(1)); 97 | c.append(rtcCenter(2)); 98 | } 99 | 100 | Json::write(os, ft, false); 101 | pad(os, offset, ' '); 102 | } 103 | 104 | } // namespace detail 105 | 106 | 107 | /** Write a glTF archive as a b3dm file. 108 | */ 109 | void b3dm(std::ostream &os, const gltf::Model &model 110 | , const boost::filesystem::path &srcDir 111 | , const math::Point3 &rtcCenter) 112 | { 113 | detail::Header header; 114 | 115 | // serialize and measure feature table. 116 | std::stringstream fs; 117 | detail::featureTable(fs, rtcCenter, header.byteLength); 118 | header.featureTableJSONByteLength = fs.tellp(); 119 | header.byteLength += header.featureTableJSONByteLength; 120 | 121 | // serialize and measure GLB archive 122 | std::stringstream gs; 123 | gltf::glb(gs, model, srcDir); 124 | detail::pad(gs, header.byteLength, 0); 125 | header.byteLength += gs.tellp(); 126 | 127 | // paste together 128 | detail::write(os, header); 129 | os << fs.rdbuf(); 130 | os << gs.rdbuf(); 131 | } 132 | 133 | /** Write a glTF archive as a b3dm file. 134 | */ 135 | void b3dm(const boost::filesystem::path &path, const gltf::Model &model 136 | , const boost::filesystem::path &srcDir 137 | , const math::Point3 &rtcCenter) 138 | { 139 | LOG(info1) << "Generating b3dm in " << path << "."; 140 | utility::ofstreambuf os(path.string()); 141 | b3dm(os, model, srcDir, rtcCenter); 142 | os.close(); 143 | } 144 | 145 | namespace detail { 146 | 147 | typedef std::array Uint32Buffer; 148 | 149 | struct Legacy { 150 | enum class Type { none, json, gltf }; 151 | Type type = Type::none; 152 | Uint32Buffer buffer; 153 | }; 154 | 155 | /** Reads b3dm header. 156 | */ 157 | Legacy read(std::istream &is, Header &header 158 | , const boost::filesystem::path &path) 159 | { 160 | char magic[4]; 161 | bin::read(is, magic); 162 | if (std::memcmp(magic, MAGIC, sizeof(MAGIC))) { 163 | LOGTHROW(err2, std::runtime_error) 164 | << "File " << path << " is not a Batched 3D Model file."; 165 | } 166 | 167 | header.version = bin::read(is); 168 | header.byteLength = bin::read(is); 169 | header.featureTableJSONByteLength = bin::read(is); 170 | header.featureTableBinaryByteLength = bin::read(is); 171 | 172 | // OK, legacy format ends here, we have to deal with old data 173 | // TODO: detect old format 174 | Legacy legacy; 175 | 176 | header.batchTableJSONByteLength = bin::read(is); 177 | header.batchTableBinaryByteLength = bin::read(is); 178 | 179 | return legacy; 180 | } 181 | 182 | void readFeatureTable(std::istream &is, const Header &header 183 | , const boost::filesystem::path &path 184 | , math::Point3 &rtcCenter) 185 | { 186 | if (!header.featureTableJSONByteLength) { return; } 187 | 188 | Json::Value ft; 189 | { 190 | // read feature table to temporary buffer 191 | std::vector buf(header.featureTableJSONByteLength); 192 | bin::read(is, buf); 193 | 194 | // wrap buffer to a stream 195 | bio::stream_buffer 196 | buffer(buf.data(), buf.data() + buf.size()); 197 | std::istream s(&buffer); 198 | s.exceptions(std::ios::badbit | std::ios::failbit); 199 | 200 | // and read 201 | if (!read(s, ft)) { 202 | LOGTHROW(err2, std::runtime_error) 203 | << "Unable to read JSON feature table from file " 204 | << path << "."; 205 | } 206 | } 207 | 208 | if (ft.isMember("RTC_CENTER")) { 209 | const auto &rc(ft["RTC_CENTER"]); 210 | if (rc.size() != 3) { 211 | LOGTHROW(err2, std::runtime_error) 212 | << "Feature table from file " 213 | << path << " has malformed RTC_CENTER."; 214 | } 215 | rtcCenter(0) = rc[0].asDouble(); 216 | rtcCenter(1) = rc[1].asDouble(); 217 | rtcCenter(2) = rc[2].asDouble(); 218 | } 219 | } 220 | 221 | } // namespace detail 222 | 223 | BatchedModel b3dm(std::istream &is, const boost::filesystem::path &path) 224 | { 225 | BatchedModel model; 226 | 227 | detail::Header header; 228 | const auto legacy(read(is, header, path)); 229 | 230 | readFeatureTable(is, header, path, model.rtcCenter); 231 | 232 | // ignore rest of tables 233 | is.ignore(header.featureTableBinaryByteLength 234 | + header.batchTableJSONByteLength 235 | + header.batchTableBinaryByteLength); 236 | 237 | model.model = gltf::glb 238 | (is, path, (legacy.type == detail::Legacy::Type::gltf)); 239 | 240 | return model; 241 | } 242 | 243 | BatchedModel b3dm(const boost::filesystem::path &path) 244 | { 245 | utility::ifstreambuf f(path.string()); 246 | auto model(b3dm(f, path)); 247 | f.close(); 248 | return model; 249 | } 250 | 251 | void loadMesh(gltf::MeshLoader &loader, const BatchedModel &model 252 | , gltf::MeshLoader::DecodeOptions options) 253 | { 254 | // add rtc and Y-up to Z-up switch 255 | options.trafo = prod(options.trafo, math::translate(model.rtcCenter)); 256 | options.trafo = prod(options.trafo, gltf::yup2zup()); 257 | 258 | decodeMesh(loader, model.model, options); 259 | } 260 | 261 | } // namespace threedtiles 262 | -------------------------------------------------------------------------------- /3dtiles/encoder.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include 28 | #include 29 | 30 | #include "utility/cppversion.hpp" 31 | 32 | #include "geo/srsdef.hpp" 33 | 34 | #include "support.hpp" 35 | #include "encoder.hpp" 36 | 37 | namespace po = boost::program_options; 38 | namespace fs = boost::filesystem; 39 | 40 | namespace threedtiles { 41 | 42 | void Encoder::Config::configuration(po::options_description &config) 43 | { 44 | config.add_options() 45 | ("geometricErrorFactor", po::value(&geometricErrorFactor) 46 | ->default_value(geometricErrorFactor)->required() 47 | , "Factor applied to pixelSize to obtain geometric error.") 48 | 49 | ("tilesetLimit", po::value(&tilesetLimit) 50 | ->default_value(tilesetLimit)->required() 51 | , "Limit for tiles in single tileset.json file. " 52 | "If generated tileset is larger it is split into multiple " 53 | "sub-tilesets.") 54 | 55 | ("parallel" 56 | , po::value(¶llel)->default_value(true)->required() 57 | , "Process in parallel.") 58 | ; 59 | } 60 | 61 | void Encoder::Config::configure(const po::variables_map &vars) 62 | { 63 | (void) vars; 64 | } 65 | 66 | namespace { 67 | 68 | double texelSize(const vts::Mesh &mesh, const vts::Atlas &atlas) 69 | { 70 | const auto ma(area(mesh)); 71 | double meshArea(ma.mesh); 72 | 73 | double textureArea(0.0); 74 | 75 | // mesh and texture area -> texelSize 76 | auto ita(ma.submeshes.begin()); 77 | for (std::size_t i(0), e(atlas.size()); i != e; ++i, ++ita) { 78 | textureArea += ita->internalTexture * atlas.area(i); 79 | } 80 | 81 | return std::sqrt(meshArea / textureArea); 82 | } 83 | 84 | void updateBoundingVolumes(const Tile::pointer &tile 85 | , const Tile::pointer &parent) 86 | { 87 | if (!tile) { return; } 88 | 89 | // descend 90 | for (const auto &child : tile->children) { 91 | updateBoundingVolumes(child, tile); 92 | } 93 | 94 | if (parent) { 95 | update(parent->boundingVolume, tile->boundingVolume); 96 | } 97 | } 98 | 99 | double computeGeometricError(const Tile::pointer &tile 100 | , double geometricErrorFactor) 101 | { 102 | if (!tile) { return 0.0; } 103 | 104 | const auto ge(tile->geometricError * geometricErrorFactor); 105 | 106 | tile->geometricError = 0.0; 107 | 108 | // get maximum child geometric error 109 | for (const auto &child : tile->children) { 110 | tile->geometricError 111 | = std::max(tile->geometricError 112 | , computeGeometricError(child, geometricErrorFactor)); 113 | } 114 | 115 | // forward this error 116 | return ge ? ge : tile->geometricError; 117 | } 118 | 119 | struct Done { 120 | Done(std::size_t count, std::size_t total) 121 | : count(count), total(total) 122 | {} 123 | 124 | std::size_t count; 125 | std::size_t total; 126 | }; 127 | 128 | template 129 | inline std::basic_ostream& 130 | operator<<(std::basic_ostream &os, const Done &d) 131 | { 132 | if (d.total) { 133 | double percentage((100.0 * d.count) / d.total); 134 | boost::io::ios_precision_saver ps(os); 135 | return os << '#' << d.count << " of " << d.total << " (" 136 | << std::fixed << std::setprecision(2) 137 | << std::setw(6) << percentage 138 | << " % done)"; 139 | } 140 | return os << '#' << d.count; 141 | } 142 | 143 | } // namespace 144 | 145 | class Encoder::Detail { 146 | public: 147 | Detail(Encoder &owner) 148 | : owner_(owner), config_(owner_.config_) 149 | , tileset_(owner_.tileset_), output_(owner_.output_) 150 | , ti_(owner_.validTiles_), fullTree_(ti_) 151 | , generated_(), total_(ti_.count()) 152 | , srs2rad_(srs2Wgs84Rad(config_.srs)) 153 | { 154 | fullTree_.makeAbsolute().complete(); 155 | } 156 | 157 | void generate() { 158 | if (config_.parallel) { 159 | UTILITY_OMP(parallel) 160 | UTILITY_OMP(single) 161 | { 162 | process({}, {}); 163 | } 164 | } else { 165 | process({}, {}); 166 | } 167 | } 168 | 169 | private: 170 | void process(const vts::TileId &tileId, Tile *parent); 171 | 172 | Region tileVolume(const vts::Mesh &mesh) { 173 | Region region; 174 | for (const auto &sm : mesh) { 175 | for (const auto &v : sm.vertices) { 176 | math::update(region.extents, srs2rad_(v)); 177 | } 178 | } 179 | return region; 180 | } 181 | 182 | Encoder &owner_; 183 | const Config &config_; 184 | Tileset &tileset_; 185 | const fs::path output_; 186 | 187 | const vts::TileIndex &ti_; 188 | vts::TileIndex fullTree_; 189 | std::atomic generated_; 190 | std::size_t total_; 191 | 192 | Point3Convertor srs2rad_; 193 | }; 194 | 195 | void Encoder::Detail::process(const vts::TileId &tileId, Tile *parent) 196 | { 197 | struct TIDGuard { 198 | TIDGuard(const std::string &id) 199 | : old(dbglog::thread_id()) 200 | { 201 | dbglog::thread_id(id); 202 | } 203 | ~TIDGuard() { try { dbglog::thread_id(old); } catch (...) {} } 204 | 205 | const std::string old; 206 | }; 207 | 208 | if (!fullTree_.get(tileId)) { return; } 209 | 210 | TIDGuard tg(str(boost::format("tile:%s") % tileId)); 211 | 212 | // hold tile in unique_ptr 213 | auto tileHolder(std::make_unique()); 214 | 215 | // borrow tile so it can be reference even after it is stored in the parent 216 | auto *tile(tileHolder.get()); 217 | 218 | if (ti_.get(tileId)) { 219 | LOG(info2) 220 | << "Generating 3D tile from tile " << tileId << "."; 221 | 222 | auto texturedMesh(owner_.generate(tileId)); 223 | 224 | auto &t(*tile); 225 | 226 | // store this tile's texel size in geometric error 227 | t.geometricError = texelSize(*texturedMesh.mesh, *texturedMesh.atlas); 228 | 229 | t.content = boost::in_place(); 230 | 231 | t.boundingVolume = t.content->boundingVolume 232 | = tileVolume(*texturedMesh.mesh); 233 | 234 | t.content->uri = saveTile 235 | (output_, tileId, *texturedMesh.mesh, *texturedMesh.atlas) 236 | .string(); 237 | 238 | Done done(++generated_, total_); 239 | 240 | LOG(info3) 241 | << "Generated 3D tile " << done 242 | << " from tile " << tileId << "."; 243 | } 244 | 245 | // store tile in the parent 246 | UTILITY_OMP(critical(threedtiles_encoder_process_1)) 247 | { 248 | if (parent) { 249 | parent->children.push_back(std::move(tileHolder)); 250 | } else { 251 | // parent is the Tileset itself 252 | tileset_.root = std::move(tileHolder); 253 | tile->refine = Refinement::replace; 254 | } 255 | } 256 | 257 | // proces children -> go down 258 | for (auto child : vts::children(tileId)) { 259 | #ifndef _MSC_VER // omp task is not supported by msvc 260 | UTILITY_OMP(task) 261 | #endif 262 | { 263 | process(child, tile); 264 | } 265 | } 266 | } 267 | 268 | void Encoder::run() 269 | { 270 | Detail(*this).generate(); 271 | 272 | updateBoundingVolumes(tileset_.root, {}); 273 | tileset_.geometricError 274 | = computeGeometricError(tileset_.root, config_.geometricErrorFactor); 275 | 276 | // split tileset into subtrees 277 | const auto subtrees(split(tileset_, config_.tilesetLimit)); 278 | 279 | write(output_ / "tileset.json", tileset_); 280 | for (const auto &subtree : subtrees) { 281 | write(output_ / subtree.uri, subtree.tileset); 282 | } 283 | } 284 | 285 | } // namespace threedtiles 286 | -------------------------------------------------------------------------------- /3dtiles/3dtiles.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #ifndef threedtiles_3dtiles_hpp_included_ 28 | #define threedtiles_3dtiles_hpp_included_ 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "utility/enum-io.hpp" 40 | #include "utility/openmp.hpp" 41 | #include "utility/raise.hpp" 42 | 43 | #include "math/geometry_core.hpp" 44 | 45 | namespace threedtiles { 46 | 47 | using OptString = boost::optional; 48 | using ExtensionList = std::vector; 49 | using Extension = boost::any; 50 | using Extensions = std::map; 51 | 52 | UTILITY_GENERATE_ENUM_CI(Refinement, 53 | ((replace)("REPLACE")) 54 | ((add)("ADD")) 55 | ) 56 | 57 | struct CommonBase { 58 | Extensions extensions; 59 | boost::any extras; 60 | }; 61 | 62 | struct Box : CommonBase { 63 | math::Point3 center; 64 | math::Point3 x; 65 | math::Point3 y; 66 | math::Point3 z; 67 | 68 | void update(const Box &other); 69 | }; 70 | 71 | struct Region : CommonBase { 72 | math::Extents3 extents; 73 | 74 | Region() : extents(math::InvalidExtents{}) {} 75 | 76 | void update(const Region &other); 77 | }; 78 | 79 | struct Sphere : CommonBase { 80 | math::Point3 center; 81 | double radius = 0.0; 82 | 83 | void update(const Sphere &other); 84 | }; 85 | 86 | /** Bounding volume. Blank when not filled yet. Tile with blank bounding volume 87 | * cannot be serialized. 88 | * 89 | * boost::blank must stay first template argument otherwise 90 | * valid(BoundingVolume) would stop working 91 | */ 92 | using BoundingVolume = boost::variant; 93 | 94 | inline bool valid(const BoundingVolume &bv) { return bv.which(); } 95 | 96 | bool inside(const BoundingVolume &inner, const BoundingVolume &outer); 97 | 98 | std::ostream& operator<<(std::ostream &os, const Box &b); 99 | std::ostream& operator<<(std::ostream &os, const Region &r); 100 | std::ostream& operator<<(std::ostream &os, const Sphere &s); 101 | std::ostream& operator<<(std::ostream &os, const BoundingVolume &bv); 102 | 103 | using Property = boost::any; 104 | 105 | using Properties = std::map; 106 | 107 | struct TileContent : CommonBase { 108 | /** Bounding volume can be invalid. 109 | */ 110 | BoundingVolume boundingVolume; 111 | std::string uri; 112 | }; 113 | 114 | struct Tile : CommonBase { 115 | typedef std::unique_ptr pointer; 116 | typedef std::vector list; 117 | 118 | BoundingVolume boundingVolume; 119 | BoundingVolume viewerRequestVolume; 120 | double geometricError = 0.0; 121 | boost::optional refine; 122 | boost::optional transform; // serialize as column major! 123 | boost::optional content; 124 | Tile::list children; 125 | 126 | // extra comment (not stored) 127 | std::string comment; 128 | 129 | /** Return number of tiles in subtree rooted in this tile. 130 | */ 131 | std::size_t subtreeSize() const; 132 | 133 | /** Return depth of subtree. No children -> 0. 134 | */ 135 | std::size_t subtreeDepth() const; 136 | }; 137 | 138 | struct Asset : CommonBase { 139 | std::string version; 140 | OptString tilesetVersion; 141 | 142 | Asset() : version("1.0") {} 143 | }; 144 | 145 | struct Tileset : CommonBase { 146 | Asset asset; 147 | Properties properties; 148 | double geometricError = 0.0; 149 | Tile::pointer root; 150 | 151 | ExtensionList extensionsUsed; 152 | ExtensionList extensionsRequired; 153 | }; 154 | 155 | /** Write a tileset JSON file to an output stream. 156 | */ 157 | void write(std::ostream &os, const Tileset &tileset); 158 | 159 | /** Write a tileset JSON file to an output file. 160 | */ 161 | void write(const boost::filesystem::path &path, const Tileset &tileset); 162 | 163 | struct TilesetWithUri { 164 | typedef std::vector list; 165 | 166 | std::string uri; 167 | Tileset tileset; 168 | 169 | TilesetWithUri(const std::string &uri) : uri(uri) {} 170 | }; 171 | 172 | /** Splits tileset tree into multiple sub-trees. 173 | * 174 | * Tile limit is only indicative, subtree is always full pyramid unless there 175 | * are leaf nodes. 176 | * 177 | * NB: original tileset tree is modified! 178 | * 179 | * \param tileset tileset to split 180 | * \param tileLimit (soft) tile limit 181 | * \returns list of sub trees 182 | */ 183 | TilesetWithUri::list split(Tileset &tileset, std::size_t tileLimit); 184 | 185 | /** Updates one volume from the other. 186 | * If updated is valid then it has to be of the same type as the updater. 187 | */ 188 | void update(BoundingVolume &updated, const BoundingVolume &updater); 189 | 190 | /** Read tileset JSON file from an output stream. 191 | */ 192 | void read(std::istream &is, Tileset &tileset 193 | , const boost::filesystem::path &path = ""); 194 | 195 | /** Resolves all relative URIs using provided base URI and distributes various 196 | * inherited data to all tiles. Used to make tileset consistent after load. 197 | * 198 | * \param tileset tileset to update 199 | * \param baseUri base URI 200 | * \param relaxed relaxed validity check 201 | * \return tileset itself, to allow call chaining 202 | */ 203 | Tileset& absolutize(Tileset &ts, const std::string &baseUri 204 | , bool relaxed = false); 205 | 206 | /** Path in tile tree. 207 | */ 208 | struct TilePath { 209 | std::vector path; 210 | TilePath() { path.reserve(32); /* guesstimage */ } 211 | int depth() const { return path.size(); } 212 | }; 213 | 214 | /** Recursively traverses the tile tree starting at root and calls 215 | * op(const Tile &tile, const TilePath &path) 216 | * for every encountered tile. 217 | */ 218 | template 219 | void traverse(const Tile &root, Op op); 220 | 221 | /** Recursively traverses the tile tree starting at root and calls 222 | * op(const Tile &tile, const TilePath &path) 223 | * for every encountered tile. 224 | * 225 | * Explicitly const version. 226 | */ 227 | template 228 | void ctraverse(const Tile &root, Op op); 229 | 230 | /** Recursively traverses the tile tree starting at root and calls 231 | * op(Tile &tile, int depth) 232 | * for every encountered tile. 233 | */ 234 | template 235 | void traverse(Tile &root, Op op); 236 | 237 | /** Deep copy, when needed. 238 | */ 239 | Tileset clone(const Tileset &ts); 240 | 241 | /** Dumps tileset metadata in human readable format. 242 | */ 243 | void dumpMetadata(std::ostream &os, const Tileset &ts); 244 | 245 | /** Adds given extension to extensions object. 246 | */ 247 | template 248 | void addExtension(Extensions &extensions, const Ext &extension); 249 | 250 | /** Gets given extension from extensions object. 251 | */ 252 | template 253 | Ext getExtension(const Extensions &extensions); 254 | 255 | /** Gets given extension from extensions object. 256 | * Not throwing version. 257 | */ 258 | template 259 | boost::optional getExtension(const Extensions &extensions 260 | , std::nothrow_t); 261 | 262 | /** Adds given extension to any entity. 263 | */ 264 | template 265 | void addExtension(CommonBase &any, const Ext &extension); 266 | 267 | /** Gets given extension from any entity. 268 | */ 269 | template 270 | Ext getExtension(CommonBase &any); 271 | 272 | /** Gets given extension from any entity. 273 | * Not throwing version. 274 | */ 275 | template 276 | boost::optional getExtension(CommonBase &any, std::nothrow_t); 277 | 278 | // inlines 279 | 280 | namespace detail { 281 | 282 | template 283 | void traverse(TileT &root, Op op) { 284 | struct Traverser { 285 | Op op; 286 | Traverser(Op op) : op(op) {} 287 | void process(TileT &tile, TilePath &path) { 288 | op(tile, path); 289 | 290 | int i(0); 291 | for (const auto &child : tile.children) { 292 | path.path.push_back(i++); 293 | process(*child, path); 294 | path.path.pop_back(); 295 | } 296 | } 297 | } t(op); 298 | 299 | TilePath path; 300 | t.process(root, path); 301 | } 302 | 303 | } // namespace detail 304 | 305 | template void traverse(const Tile &root, Op op) 306 | { 307 | detail::traverse(root, op); 308 | } 309 | 310 | template void ctraverse(const Tile &root, Op op) 311 | { 312 | detail::traverse(root, op); 313 | } 314 | 315 | template void traverse(Tile &root, Op op) 316 | { 317 | detail::traverse(root, op); 318 | } 319 | 320 | inline std::size_t Tile::subtreeSize() const { 321 | std::size_t count(0); 322 | traverse(*this, [&count](const Tile&, const TilePath&) { ++count; }); 323 | return count; 324 | } 325 | 326 | inline std::size_t Tile::subtreeDepth() const { 327 | std::size_t depth(0); 328 | traverse(*this, [&depth](const Tile&, const TilePath &path) { 329 | depth = std::max(depth, std::size_t(path.depth())); 330 | }); 331 | return depth; 332 | } 333 | 334 | template 335 | void addExtension(Extensions &extensions, const E &value) 336 | { 337 | Extension ext; 338 | build(ext, value); 339 | extensions.emplace(E::extensionName, ext); 340 | } 341 | 342 | template 343 | boost::optional getExtension(const Extensions &extensions, std::nothrow_t) 344 | { 345 | auto fextensions(extensions.find(E::extensionName)); 346 | if (fextensions == extensions.end()) { return boost::none; } 347 | 348 | const auto &ext(fextensions->second); 349 | 350 | E value; 351 | parse(value, ext); 352 | return value; 353 | } 354 | 355 | template 356 | E getExtension(const Extensions &extensions) 357 | { 358 | auto value(getExtension(extensions, std::nothrow)); 359 | if (!value) { 360 | throw utility::raise 361 | ("Extension <%s> not found.", E::extensionName); 362 | } 363 | return *value; 364 | } 365 | 366 | /** Adds given extension to any entity. 367 | */ 368 | template 369 | void addExtension(CommonBase &any, const Ext &extension) 370 | { 371 | return addExtension(any.extensions, extension); 372 | } 373 | 374 | /** Gets given extension from any entity. 375 | */ 376 | template 377 | Ext getExtension(CommonBase &any) 378 | { 379 | return getExtension(any.extensions); 380 | } 381 | 382 | /** Gets given extension from any entity. 383 | * Not throwing version. 384 | */ 385 | template 386 | boost::optional getExtension(CommonBase &any, std::nothrow_t) 387 | { 388 | return getExtension(any.extensions); 389 | } 390 | 391 | } // namespace threedtiles 392 | 393 | #endif // 3dtiles_3dtiles_hpp_included_ 394 | -------------------------------------------------------------------------------- /3dtiles/mesh.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include 28 | 29 | #include 30 | 31 | #include "utility/stl-helpers.hpp" 32 | #include "utility/format.hpp" 33 | #include "utility/binaryio.hpp" 34 | 35 | #include "vts-libs/vts/meshop.hpp" 36 | 37 | #include "gltf/gltf.hpp" 38 | #include "3dtiles/b3dm.hpp" 39 | 40 | #include "mesh.hpp" 41 | 42 | 43 | namespace fs = boost::filesystem; 44 | namespace vts = vtslibs::vts; 45 | namespace bin = utility::binaryio; 46 | 47 | namespace threedtiles { 48 | 49 | std::uint32_t calculateHash(const std::string &data) 50 | { 51 | boost::crc_32_type crc; 52 | crc.process_bytes(data.data(), data.size()); 53 | return crc.checksum(); 54 | } 55 | 56 | fs::path dir(const fs::path &filename) 57 | { 58 | const auto hash(calculateHash(filename.string())); 59 | return str(boost::format("%02x") % ((hash >> 24) & 0xff)); 60 | } 61 | 62 | template 63 | void writeFaceIndices(std::ostream &os, const vts::SubMesh &mesh) 64 | { 65 | for (const auto &face : mesh.faces) { 66 | bin::write(os, T(face(0))); 67 | bin::write(os, T(face(1))); 68 | bin::write(os, T(face(2))); 69 | } 70 | } 71 | 72 | gltf::Index serializeMesh(gltf::GLTF &ga, const std::string &name 73 | , const vts::SubMesh &sm 74 | , const math::Point3 ¢er 75 | , const gltf::Index &materialId) 76 | { 77 | const gltf::Index nodeId(ga.nodes.size()); 78 | auto &node(utility::append(ga.nodes)); 79 | 80 | const gltf::Index omeshId(ga.meshes.size()); 81 | auto &omesh(utility::append(ga.meshes)); 82 | node.mesh = omeshId; 83 | 84 | const gltf::Index bufferId(ga.buffers.size()); 85 | auto &buffer(boost::get 86 | (utility::append(ga.buffers, gltf::InlineBuffer()))); 87 | 88 | // make room for all 3 buffer views to prevent reallocation 89 | ga.bufferViews.reserve(ga.bufferViews.size() + 3); 90 | const gltf::Index facesId(ga.bufferViews.size()); 91 | auto &faces(utility::append(ga.bufferViews, bufferId)); 92 | 93 | const gltf::Index verticesId(ga.bufferViews.size()); 94 | auto &vertices(utility::append(ga.bufferViews, bufferId)); 95 | 96 | const gltf::Index tcId(ga.bufferViews.size()); 97 | auto &tc(utility::append(ga.bufferViews, bufferId)); 98 | 99 | node.name = name; 100 | omesh.name = name; 101 | buffer.name = name; 102 | 103 | // use ubyte, ushort or uint depending on the number of vertices 104 | const auto vertexIndexSize([&]() -> std::size_t 105 | { 106 | if (sm.vertices.size() < (1 << 8)) { return 1; } 107 | if (sm.vertices.size() < (1 << 16)) { return 2; } 108 | return 4; 109 | }()); 110 | 111 | const auto &extents(math::computeExtents(sm.vertices)); 112 | const math::Extents3 localExtents 113 | (extents.ll - center, extents.ur - center); 114 | 115 | { 116 | std::ostringstream os; 117 | auto currentPosition([&]() -> std::size_t { return os.tellp(); }); 118 | 119 | // well, not nice, but does the job... 120 | auto align([&](std::size_t alignment) 121 | { 122 | auto current(currentPosition()); 123 | while (current % alignment) { 124 | bin::write(os, std::uint8_t()); 125 | ++current; 126 | } 127 | return current; 128 | }); 129 | 130 | faces.byteOffset = currentPosition(); 131 | 132 | // 1) write faces 133 | switch (vertexIndexSize) { 134 | case 1: writeFaceIndices(os, sm); break; 135 | case 2: writeFaceIndices(os, sm); break; 136 | case 4: writeFaceIndices(os, sm); break; 137 | } 138 | 139 | faces.byteLength = currentPosition() - faces.byteOffset; 140 | vertices.byteOffset = align(sizeof(float)); 141 | 142 | // 2) write vertices 143 | for (const auto &v : sm.vertices) { 144 | // translate to origin 145 | const math::Point3 p(v - center); 146 | 147 | bin::write(os, float(p(0))); 148 | bin::write(os, float(p(1))); 149 | bin::write(os, float(p(2))); 150 | } 151 | 152 | vertices.byteLength = currentPosition() - vertices.byteOffset; 153 | tc.byteOffset = align(sizeof(float)); 154 | 155 | // 3) write texture coordinates 156 | for (const auto &tc : sm.tc) { 157 | bin::write(os, float(tc(0))); 158 | bin::write(os, float(1.0 - tc(1))); 159 | } 160 | 161 | { 162 | const auto data(os.str()); 163 | buffer.data.assign(data.begin(), data.end()); 164 | } 165 | 166 | tc.byteLength = buffer.data.size() - tc.byteOffset; 167 | } 168 | 169 | // mesh primitives 170 | auto &primitive(utility::append(omesh.primitives)); 171 | primitive.mode = gltf::PrimitiveMode::triangles; 172 | primitive.material = materialId; 173 | 174 | // create buffer views and their accessors 175 | { 176 | auto &bv(faces); 177 | bv.target = gltf::Target::elementArrayBuffer; 178 | bv.name = utility::concat(name, ".faces"); 179 | 180 | const gltf::Index aId(ga.accessors.size()); 181 | primitive.indices = aId; 182 | 183 | auto &a(utility::append(ga.accessors)); 184 | a.bufferView = facesId; 185 | 186 | switch (vertexIndexSize) { 187 | case 1: a.componentType = gltf::ComponentType::ubyte; break; 188 | case 2: a.componentType = gltf::ComponentType::ushort; break; 189 | case 4: a.componentType = gltf::ComponentType::uint; break; 190 | } 191 | 192 | a.count = 3 * sm.faces.size(); 193 | a.type = gltf::AttributeType::scalar; 194 | a.name = bv.name; 195 | 196 | } 197 | 198 | { 199 | auto &bv(vertices); 200 | bv.buffer = bufferId; 201 | bv.target = gltf::Target::arrayBuffer; 202 | bv.name = utility::concat(name, ".vertices"); 203 | 204 | const gltf::Index aId(ga.accessors.size()); 205 | 206 | auto &a(utility::append(ga.accessors)); 207 | a.bufferView = verticesId; 208 | 209 | a.componentType = gltf::ComponentType::float_; 210 | a.count = sm.vertices.size(); 211 | a.type = gltf::AttributeType::vec3; 212 | a.max = { localExtents.ur(0), localExtents.ur(1) 213 | , localExtents.ur(2) }; 214 | a.min = { localExtents.ll(0), localExtents.ll(1) 215 | , localExtents.ll(2) }; 216 | a.name = bv.name; 217 | 218 | primitive.attributes 219 | [gltf::AttributeSemantic::position] = aId; 220 | } 221 | 222 | { 223 | auto &bv(tc); 224 | bv.buffer = bufferId; 225 | bv.target = gltf::Target::arrayBuffer; 226 | bv.name = utility::concat(name, ".tc"); 227 | 228 | const gltf::Index aId(ga.accessors.size()); 229 | auto &a(utility::append(ga.accessors)); 230 | a.bufferView = tcId; 231 | 232 | a.componentType = gltf::ComponentType::float_; 233 | a.count = sm.tc.size(); 234 | a.type = gltf::AttributeType::vec2; 235 | a.name = bv.name; 236 | 237 | primitive.attributes 238 | [gltf::AttributeSemantic::texCoord0] = aId; 239 | } 240 | 241 | return nodeId; 242 | } 243 | 244 | gltf::Index serializeTexture(gltf::GLTF &ga, const std::string &name 245 | , const ImageSource &images, int idx 246 | , gltf::Index simpleSamplerId) 247 | { 248 | const gltf::Index imageId(ga.images.size()); 249 | ga.images.push_back(std::move(images.image(idx))); 250 | 251 | const gltf::Index textureId(ga.textures.size()); 252 | const gltf::Index materialId(ga.materials.size()); 253 | 254 | utility::append(ga.textures, simpleSamplerId, imageId); 255 | auto &material(utility::append(ga.materials)); 256 | 257 | material.name = name; 258 | material.pbrMetallicRoughness = boost::in_place(); 259 | { 260 | auto &pbr(*material.pbrMetallicRoughness); 261 | 262 | // non-metalic behaviour if unlit extension doesn't kick in 263 | pbr.metallicFactor = 0.0; 264 | 265 | pbr.baseColorTexture = boost::in_place(); 266 | auto &ti(*pbr.baseColorTexture); 267 | ti.index = textureId; 268 | } 269 | 270 | // make it unlit 271 | material.extensions["KHR_materials_unlit"] = gltf::emptyObject(); 272 | 273 | return materialId; 274 | } 275 | 276 | boost::filesystem::path 277 | tilePath(const vtslibs::vts::TileId &tileId 278 | , const std::string &extension 279 | , const boost::optional &z) 280 | { 281 | const fs::path fname 282 | (z 283 | ? utility::format("%s-%s-%s-%s%s" 284 | , tileId.lod, tileId.x, tileId.y, *z, extension) 285 | : utility::format("%s-%s-%s%s" 286 | , tileId.lod, tileId.x, tileId.y, extension)); 287 | 288 | return dir(fname) / fname; 289 | } 290 | 291 | namespace detail { 292 | 293 | template 294 | void saveTile(Sink &&sink 295 | , const boost::filesystem::path &path 296 | , const vtslibs::vts::TileId &tileId 297 | , const vtslibs::vts::ConstSubMeshRange &submeshes 298 | , const ImageSource &images) 299 | { 300 | if (submeshes.size() != submeshes.total()) { 301 | LOG(info1) << "Saving tile " << tileId 302 | << ", submeshes: [" << submeshes.b << ", " 303 | << submeshes.e << ") of " << submeshes.total() 304 | << " to " << path << "."; 305 | 306 | if (submeshes.empty()) { 307 | LOGTHROW(err2, std::runtime_error) 308 | << "Empty or invalid submesh range. Nothing to save."; 309 | } 310 | 311 | } else { 312 | LOG(info1) << "Saving tile " << tileId 313 | << ", submeshes: " << submeshes.total() 314 | << " to " << path << "."; 315 | } 316 | 317 | gltf::GLTF ga; 318 | const auto simpleSamplerId(gltf::add(ga.samplers)); 319 | ga.extensionsUsed.push_back("KHR_materials_unlit"); 320 | 321 | const auto me(vts::extents(submeshes)); 322 | const auto mc(math::center(me)); 323 | 324 | gltf::Indices nodes; 325 | 326 | auto idx(submeshes.b); 327 | for (const auto &inSm : submeshes) { 328 | // ensure mesh uses the same vertex indices in 3D and 2D faces 329 | const auto sm(vts::makeSharedFaces(inSm)); 330 | 331 | LOG(info1) << "Saving submesh " << idx << " of tile " << tileId 332 | << ", vertices: " << sm.vertices.size() 333 | << ", faces: " << sm.faces.size() 334 | << ", texture: " << images.info(idx); 335 | 336 | const auto name(utility::format("%s.%s", tileId, idx)); 337 | const auto materialId 338 | (serializeTexture(ga, name, images, idx, simpleSamplerId)); 339 | const auto nodeId(serializeMesh(ga, name, sm, mc, materialId)); 340 | nodes.push_back(nodeId); 341 | 342 | ++idx; 343 | } 344 | 345 | { 346 | const gltf::Index rootId(ga.nodes.size()); 347 | ga.defaultScene().nodes.push_back(rootId); 348 | auto &rootNode(utility::append(ga.nodes)); 349 | rootNode.matrix = gltf::zup2yup(); 350 | rootNode.name = utility::format("%s", tileId); 351 | rootNode.children = nodes; 352 | } 353 | 354 | b3dm(sink, ga, {}, mc); 355 | } 356 | 357 | } // namespace detail 358 | 359 | void saveTile(std::ostream &os, const boost::filesystem::path &path 360 | , const vtslibs::vts::TileId &tileId 361 | , const vtslibs::vts::ConstSubMeshRange &submeshes 362 | , const ImageSource &images) 363 | { 364 | detail::saveTile(os, path, tileId, submeshes, images); 365 | } 366 | 367 | fs::path saveTile(const fs::path &root, const vts::TileId &tileId 368 | , const vtslibs::vts::ConstSubMeshRange &submeshes 369 | , const ImageSource &images 370 | , const boost::optional &z) 371 | { 372 | const auto path(tilePath(tileId, ".b3dm", z)); 373 | const auto fullPath(root / path); 374 | 375 | fs::create_directories(fullPath.parent_path()); 376 | 377 | detail::saveTile(fullPath, fullPath, tileId, submeshes, images); 378 | 379 | return path; 380 | } 381 | 382 | namespace detail { 383 | 384 | gltf::Image AtlasSource::image(int idx) const 385 | { 386 | gltf::InlineImage image; 387 | { 388 | std::ostringstream os; 389 | atlas_.write(os, idx); 390 | const auto str(os.str()); 391 | image.data.assign(str.data(), str.data() + str.size()); 392 | } 393 | 394 | // TODO: get mime-type from atlas 395 | image.mimeType = "image/jpeg"; 396 | return image; 397 | } 398 | 399 | std::string AtlasSource::info(int idx) const 400 | { 401 | std::ostringstream os; 402 | os << atlas_.imageSize(idx); 403 | return os.str(); 404 | } 405 | 406 | } // namespace detail 407 | 408 | } // namespace threedtiles 409 | -------------------------------------------------------------------------------- /3dtiles/3dtiles.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Melown Technologies SE 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * * Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | * POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | #include "dbglog/dbglog.hpp" 34 | 35 | #include "utility/format.hpp" 36 | 37 | #include "math/transform.hpp" 38 | 39 | #include "utility/cppversion.hpp" 40 | #include "utility/streams.hpp" 41 | #include "utility/format.hpp" 42 | #include "utility/uri.hpp" 43 | 44 | #include "jsoncpp/as.hpp" 45 | #include "jsoncpp/io.hpp" 46 | 47 | #include "3dtiles.hpp" 48 | #include "io.hpp" 49 | 50 | namespace fs = boost::filesystem; 51 | namespace ba = boost::algorithm; 52 | 53 | namespace threedtiles { 54 | 55 | namespace detail { 56 | 57 | void build(Json::Value &value, const Tile &tile); 58 | 59 | template 60 | typename std::enable_if::value, void>::type 61 | build(Json::Value &object, const T &value); 62 | 63 | template 64 | void build(Json::Value &object, const char *name 65 | , const boost::optional &value); 66 | 67 | /** We do not support optional BoundingVolume 68 | */ 69 | template <> 70 | void build(Json::Value &object, const char *name 71 | , const boost::optional &value) 72 | = delete; 73 | 74 | template 75 | void build(Json::Value &object, const char *name 76 | , const std::unique_ptr &value); 77 | 78 | template 79 | void build(Json::Value &value, const std::vector &list); 80 | 81 | template 82 | void build(Json::Value &object, const char *name, const std::vector &list); 83 | 84 | template 85 | void build(Json::Value &object, const char *name 86 | , const std::map &map); 87 | 88 | 89 | /** Any is expected to be Json::Value instance. 90 | */ 91 | void build(Json::Value &object, const char *name, const boost::any &value) 92 | { 93 | if (const auto *valueObject = boost::any_cast(&value)) { 94 | object[name] = *valueObject; 95 | } 96 | } 97 | 98 | void build(Json::Value &object, const math::Matrix4 &matrix) 99 | { 100 | // dump matrix as a column major 101 | object = Json::arrayValue; 102 | for (int column(0); column < 4; ++column) { 103 | for (int row(0); row < 4; ++row) { 104 | object.append(matrix(row, column)); 105 | } 106 | } 107 | } 108 | 109 | void build(Json::Value &object, const math::Point2d &p) 110 | { 111 | object = Json::arrayValue; 112 | object.append(p(0)); 113 | object.append(p(1)); 114 | } 115 | 116 | void build(Json::Value &object, const math::Point3d &p) 117 | { 118 | object = Json::arrayValue; 119 | object.append(p(0)); 120 | object.append(p(1)); 121 | object.append(p(2)); 122 | } 123 | 124 | void build(Json::Value &object, const math::Point4d &p) 125 | { 126 | object = Json::arrayValue; 127 | object.append(p(0)); 128 | object.append(p(1)); 129 | object.append(p(2)); 130 | object.append(p(3)); 131 | } 132 | 133 | void common(Json::Value &value, const CommonBase &cb) 134 | { 135 | value = Json::objectValue; 136 | build(value, "extensions", cb.extensions); 137 | build(value, "extras", cb.extras); 138 | } 139 | 140 | void build(Json::Value &object, Refinement refinement) 141 | { 142 | object = boost::lexical_cast(refinement); 143 | } 144 | 145 | void build(Json::Value &value, const BoundingVolume &bv) 146 | { 147 | struct Builder : public boost::static_visitor { 148 | Json::Value &value; 149 | Builder(Json::Value &value) : value(value) { 150 | value = Json::objectValue; 151 | } 152 | 153 | void operator()(const Box &b) { 154 | auto &array(value["box"] = Json::arrayValue); 155 | 156 | array.append(b.center(0)); 157 | array.append(b.center(1)); 158 | array.append(b.center(2)); 159 | array.append(b.x(0)); 160 | array.append(b.x(1)); 161 | array.append(b.x(2)); 162 | array.append(b.y(0)); 163 | array.append(b.y(1)); 164 | array.append(b.y(2)); 165 | array.append(b.z(0)); 166 | array.append(b.z(1)); 167 | array.append(b.z(2)); 168 | } 169 | 170 | void operator()(const Region &r) { 171 | auto &array(value["region"] = Json::arrayValue); 172 | 173 | array.append(r.extents.ll(0)); 174 | array.append(r.extents.ll(1)); 175 | array.append(r.extents.ur(0)); 176 | array.append(r.extents.ur(1)); 177 | array.append(r.extents.ll(2)); 178 | array.append(r.extents.ur(2)); 179 | } 180 | 181 | void operator()(const Sphere &s) { 182 | auto &array(value["sphere"] = Json::arrayValue); 183 | 184 | array.append(s.center(0)); 185 | array.append(s.center(1)); 186 | array.append(s.center(2)); 187 | array.append(s.radius); 188 | } 189 | 190 | void operator()(const boost::blank&) { 191 | LOGTHROW(err2, std::runtime_error) 192 | << "Invalid bounding volume cannot be serialized."; 193 | } 194 | } builder(value); 195 | boost::apply_visitor(builder, bv); 196 | } 197 | 198 | void build(Json::Value &value, const char *name, const BoundingVolume &bv) 199 | { 200 | if (valid(bv)) { build(value[name], bv); } 201 | } 202 | 203 | void build(Json::Value &value, const TileContent &content) 204 | { 205 | common(value, content); 206 | build(value["uri"], content.uri); 207 | build(value, "boundingVolume", content.boundingVolume); 208 | } 209 | 210 | template 211 | typename std::enable_if::value, void>::type 212 | build(Json::Value &object, const T &value) 213 | { 214 | object = static_cast(value); 215 | } 216 | 217 | template 218 | typename std::enable_if::value, void>::type 219 | build(Json::Value &object, const T &value) 220 | { 221 | object = value; 222 | } 223 | 224 | template <> 225 | void build(Json::Value &object, const std::size_t &value) 226 | { 227 | object = Json::UInt64(value); 228 | } 229 | 230 | template 231 | void build(Json::Value &object, const char *name 232 | , const boost::optional &value) 233 | { 234 | if (value) { build(object[name], *value); } 235 | } 236 | 237 | template 238 | void build(Json::Value &object, const char *name 239 | , const std::unique_ptr &value) 240 | { 241 | if (value) { build(object[name], *value); } 242 | } 243 | 244 | template 245 | void build(Json::Value &object, const std::unique_ptr &value) 246 | { 247 | build(object, *value); 248 | } 249 | 250 | template 251 | void build(Json::Value &value, const std::vector &list) 252 | { 253 | value = Json::arrayValue; 254 | for (const auto &element : list) { 255 | build(value.append({}), element); 256 | } 257 | } 258 | 259 | template 260 | void build(Json::Value &object, const char *name, const std::vector &list) 261 | { 262 | if (list.empty()) { return; } 263 | 264 | auto &value(object[name] = Json::arrayValue); 265 | for (const auto &element : list) { 266 | build(value.append({}), element); 267 | } 268 | } 269 | 270 | template 271 | void build(Json::Value &object, const char *name 272 | , const std::map &map) 273 | { 274 | if (map.empty()) { return; } 275 | 276 | auto &value(object[name] = Json::objectValue); 277 | for (const auto &element : map) { 278 | build(value, element.first.c_str(), element.second); 279 | } 280 | } 281 | 282 | void build(Json::Value &value, const Tile &tile) 283 | { 284 | common(value, tile); 285 | build(value["boundingVolume"], tile.boundingVolume); 286 | build(value, "viewerRequestVolume", tile.viewerRequestVolume); 287 | build(value["geometricError"], tile.geometricError); 288 | build(value, "refine", tile.refine); 289 | build(value, "transform", tile.transform); 290 | 291 | build(value, "content", tile.content); 292 | build(value, "children", tile.children); 293 | } 294 | 295 | void build(Json::Value &value, const Asset &asset) 296 | { 297 | build(value["version"], asset.version); 298 | build(value, "tilesetVersion", asset.tilesetVersion); 299 | } 300 | 301 | void build(Json::Value &value, const Tileset &tileset) 302 | { 303 | common(value, tileset); 304 | 305 | build(value["asset"], tileset.asset); 306 | build(value, "root", tileset.root); 307 | build(value["geometricError"], tileset.geometricError); 308 | 309 | build(value, "extensionsUsed", tileset.extensionsUsed); 310 | build(value, "extensionsRequired", tileset.extensionsRequired); 311 | } 312 | 313 | template 314 | bool update(BoundingVolume &updated, const BoundingVolume &updater) 315 | { 316 | if (auto *dst = boost::get(&updated)) { 317 | if (auto *src = boost::get(&updater)) { 318 | dst->update(*src); 319 | return true; 320 | } 321 | LOGTHROW(err2, std::runtime_error) 322 | << "Bounding volume can be updated only with another bounding " 323 | "volume of the same type."; 324 | } 325 | return false; 326 | } 327 | 328 | } // namespace detail 329 | 330 | void write(std::ostream &os, const Tileset &tileset) 331 | { 332 | Json::Value value; 333 | detail::build(value, tileset); 334 | Json::write(os, value, false); 335 | } 336 | 337 | void write(const fs::path &path, const Tileset &tileset) 338 | { 339 | LOG(info1) << "Saving Tileset to " << path << "."; 340 | std::ofstream f; 341 | f.exceptions(std::ios::badbit | std::ios::failbit); 342 | f.open(path.string(), std::ios_base::out); 343 | write(f, tileset); 344 | f.close(); 345 | } 346 | 347 | void update(BoundingVolume &updated, const BoundingVolume &updater) 348 | { 349 | if (!valid(updater)) { 350 | // invalid updater -> no-op 351 | return; 352 | } 353 | 354 | if (!valid(updated)) { 355 | // invalid updated -> copy 356 | updated = updater; 357 | return; 358 | } 359 | 360 | if (detail::update(updated, updater)) { return; } 361 | if (detail::update(updated, updater)) { return; } 362 | if (detail::update(updated, updater)) { return; } 363 | } 364 | 365 | void Box::update(const Box&) 366 | { 367 | LOGTHROW(err2, std::runtime_error) 368 | << "Update of box bounding volume not implemented yet."; 369 | } 370 | 371 | void Region::update(const Region &other) 372 | { 373 | math::update(extents, other.extents.ll); 374 | math::update(extents, other.extents.ur); 375 | } 376 | 377 | void Sphere::update(const Sphere&) 378 | { 379 | LOGTHROW(err2, std::runtime_error) 380 | << "Update of sphere bounding volume not implemented yet."; 381 | } 382 | 383 | namespace detail { 384 | 385 | void common(CommonBase &cb, const Json::Value &value) 386 | { 387 | if (value.isMember("extensions")) { 388 | const auto &extensions(value["extensions"]); 389 | for (const auto &name : extensions.getMemberNames()) { 390 | cb.extensions.insert(Extensions::value_type 391 | (name, extensions[name])); 392 | } 393 | } 394 | if (value.isMember("extras")) { 395 | cb.extras = value["extras"]; 396 | } 397 | } 398 | 399 | template 400 | void parse(boost::optional &dst, const Json::Value &value 401 | , const char *member); 402 | 403 | template 404 | void parse(std::vector &dst, const Json::Value &value 405 | , const char *member); 406 | 407 | void parse(std::vector &list, const Json::Value &value) 408 | { 409 | if (!value.isArray()) { 410 | LOGTHROW(err1, Json::Error) 411 | << "Expected JSON array."; 412 | } 413 | 414 | for (const auto &item : value) { 415 | if (!item.isString()) { 416 | LOGTHROW(err1, Json::Error) 417 | << "Expected JSON array of strings."; 418 | } 419 | list.push_back(item.asString()); 420 | } 421 | } 422 | 423 | void parse(std::vector &list, const Json::Value &value 424 | , const char *member) 425 | { 426 | if (!value.isMember(member)) { return; } 427 | parse(list, value["member"]); 428 | } 429 | 430 | void parse(Box &box, const Json::Value &value) 431 | { 432 | if ((value.type() != Json::arrayValue) || (value.size() != 12)) { 433 | LOGTHROW(err1, Json::Error) 434 | << "Box doesn't have 12 elements (but " << value.size() << ")."; 435 | } 436 | 437 | Json::get(box.center(0), value[0]); 438 | Json::get(box.center(1), value[1]); 439 | Json::get(box.center(2), value[2]); 440 | Json::get(box.x(0), value[3]); 441 | Json::get(box.x(1), value[4]); 442 | Json::get(box.x(2), value[5]); 443 | Json::get(box.y(0), value[6]); 444 | Json::get(box.y(1), value[7]); 445 | Json::get(box.y(2), value[8]); 446 | Json::get(box.z(0), value[9]); 447 | Json::get(box.z(1), value[10]); 448 | Json::get(box.z(2), value[11]); 449 | } 450 | 451 | void parse(Region ®ion, const Json::Value &value) 452 | { 453 | if ((value.type() != Json::arrayValue) || (value.size() != 6)) { 454 | LOGTHROW(err1, Json::Error) 455 | << "Region doesn't have 6 elements (but " << value.size() << ")."; 456 | } 457 | 458 | Json::get(region.extents.ll(0), value[0]); 459 | Json::get(region.extents.ll(1), value[1]); 460 | Json::get(region.extents.ur(0), value[2]); 461 | Json::get(region.extents.ur(1), value[3]); 462 | Json::get(region.extents.ll(2), value[4]); 463 | Json::get(region.extents.ur(2), value[5]); 464 | } 465 | 466 | void parse(Sphere &sphere, const Json::Value &value) 467 | { 468 | if ((value.type() != Json::arrayValue) || (value.size() != 4)) { 469 | LOGTHROW(err1, Json::Error) 470 | << "Sphere doesn't have 4 elements (but " << value.size() << ")."; 471 | } 472 | 473 | Json::get(sphere.center(0), value[0]); 474 | Json::get(sphere.center(1), value[1]); 475 | Json::get(sphere.center(2), value[2]); 476 | Json::get(sphere.radius, value[3]); 477 | } 478 | 479 | void parse(BoundingVolume &bv, const Json::Value &value) 480 | { 481 | if (value.isMember("box")) { 482 | Box box; 483 | parse(box, value["box"]); 484 | bv = box; 485 | } else if (value.isMember("region")) { 486 | Region region; 487 | parse(region, value["region"]); 488 | bv = region; 489 | } else if (value.isMember("sphere")) { 490 | Sphere sphere; 491 | parse(sphere, value["sphere"]); 492 | bv = sphere; 493 | } else { 494 | LOGTHROW(err1, Json::Error) 495 | << "Invalid boundingVolume."; 496 | } 497 | } 498 | 499 | /** Bounding volume may be (by default) invalid, so here comes special 500 | * "optionsl" handling. 501 | */ 502 | void parse(BoundingVolume &bv, const Json::Value &value, const char *member) 503 | { 504 | if (value.isMember(member)) { 505 | parse(bv, value[member]); 506 | } 507 | } 508 | 509 | void parse(Refinement &refinement, const Json::Value &value) 510 | { 511 | refinement = boost::lexical_cast(value.asString()); 512 | } 513 | 514 | void parse(math::Matrix4 &matrix, const Json::Value &value) 515 | { 516 | if ((value.type() != Json::arrayValue) || (value.size() != 16)) { 517 | LOGTHROW(err1, Json::Error) 518 | << "Transformation matrix doesn't have 16 elements (but" 519 | << value.size() << ")."; 520 | } 521 | 522 | matrix = boost::numeric::ublas::zero_matrix(4, 4); 523 | 524 | // read matrix as a column major order 525 | for (int i(0), column(0); column < 4; ++column) { 526 | for (int row(0); row < 4; ++row, ++i) { 527 | matrix(row, column) = value[i].asDouble(); 528 | } 529 | } 530 | } 531 | 532 | void parse(TileContent &content, const Json::Value &value) 533 | { 534 | common(content, value); 535 | // support both 1.0 uri and 0.0 url 536 | Json::get(content.uri, value, { "uri", "url" }); 537 | parse(content.boundingVolume, value, "boundingVolume"); 538 | } 539 | 540 | void parse(Tile &tile, const Json::Value &value) 541 | { 542 | common(tile, value); 543 | parse(tile.boundingVolume, value["boundingVolume"]); 544 | 545 | parse(tile.viewerRequestVolume, value, "viewerRequestVolume"); 546 | parse(tile.content, value, "content"); 547 | 548 | // treat geometric-error as optional, do not warn if it seams that tile is 549 | // an external tileset 550 | if (!Json::getOpt(tile.geometricError, value, "geometricError")) { 551 | if (!tile.content || !ba::iends_with(tile.content->uri, ".json")) { 552 | LOG(warn2) << "Missing geometricError, skipping."; 553 | } 554 | } 555 | 556 | parse(tile.refine, value, "refine"); 557 | parse(tile.transform, value, "transform"); 558 | parse(tile.children, value, "children"); 559 | } 560 | 561 | void parse(std::vector &tiles, const Json::Value &value) 562 | { 563 | if (!value.isArray()) { 564 | LOGTHROW(err1, Json::Error) 565 | << "Expected JSON array."; 566 | } 567 | 568 | for (const auto &item : value) { 569 | tiles.push_back(std::make_unique()); 570 | parse(*tiles.back(), item); 571 | } 572 | } 573 | 574 | void parse(Asset &asset, const Json::Value &value) 575 | { 576 | Json::get(asset.version, value, "version"); 577 | Json::get(asset.tilesetVersion, value, "tilesetVersion"); 578 | // TODO: gltfUpAxis (version 0.0) 579 | } 580 | 581 | void parse(Tileset &tileset, const Json::Value &value) 582 | { 583 | common(tileset, value); 584 | 585 | parse(tileset.asset, value["asset"]); 586 | // TODO: properties 587 | parse(*(tileset.root = std::make_unique()), value["root"]); 588 | Json::get(tileset.geometricError, value, "geometricError"); 589 | 590 | parse(tileset.extensionsUsed, value, "extensionsUsed"); 591 | parse(tileset.extensionsRequired, value, "extensionsRequired"); 592 | } 593 | 594 | template 595 | void parse(boost::optional &dst, const Json::Value &value 596 | , const char *member) 597 | { 598 | if (value.isMember(member)) { 599 | parse(*(dst = boost::in_place()), value[member]); 600 | } 601 | } 602 | 603 | template 604 | void parse(std::vector &dst, const Json::Value &value 605 | , const char *member) 606 | { 607 | if (value.isMember(member)) { 608 | parse(dst, value[member]); 609 | } 610 | } 611 | 612 | } // namespace detail 613 | 614 | void read(std::istream &is, Tileset &tileset, const fs::path &path) 615 | { 616 | auto content(Json::read(is, path, "3D Tileset")); 617 | detail::parse(tileset, content); 618 | 619 | } 620 | 621 | Tileset& absolutize(Tileset &ts, const std::string &baseUri, bool relaxed) 622 | { 623 | auto &root(*ts.root); 624 | 625 | // sanity check 626 | if (!relaxed && !root.refine) { 627 | LOGTHROW(err2, std::runtime_error) 628 | << "Root tile in tileset <" << baseUri 629 | << "> has no refinement defined."; 630 | } 631 | 632 | const utility::Uri base(baseUri); 633 | 634 | // make sure root tile has a valid transformation matrix 635 | if (!root.transform) { 636 | root.transform = math::identity4(); 637 | } 638 | 639 | traverse(*ts.root, [&](Tile &tile, const TilePath&) 640 | { 641 | // resolve URI 642 | if (auto &content = tile.content) { 643 | content->uri = str(base.resolve(utility::Uri(content->uri))); 644 | } 645 | 646 | // distribute refinement and transformation down the tree 647 | for (auto &child : tile.children) { 648 | if (!child->refine) { child->refine = tile.refine; } 649 | 650 | if (child->transform) { 651 | // compose transformation (child's trafor is applied first) 652 | child->transform = prod(*tile.transform, *child->transform); 653 | } else { 654 | // distribute 655 | child->transform = tile.transform; 656 | } 657 | } 658 | }); 659 | 660 | return ts; 661 | } 662 | 663 | namespace { 664 | 665 | void cloneInto(CommonBase &dst, const CommonBase &src) 666 | { 667 | dst = src; 668 | } 669 | 670 | Tile::pointer clone(const Tile &tile) 671 | { 672 | auto ntile(std::make_unique()); 673 | cloneInto(*ntile, tile); 674 | 675 | ntile->boundingVolume = tile.boundingVolume; 676 | ntile->viewerRequestVolume = tile.viewerRequestVolume; 677 | ntile->geometricError = tile.geometricError; 678 | ntile->refine = tile.refine; 679 | ntile->transform = tile.transform; 680 | ntile->content = tile.content; 681 | 682 | for (const auto &child : tile.children) { 683 | ntile->children.push_back(clone(*child)); 684 | } 685 | 686 | return ntile; 687 | } 688 | 689 | } // namespace 690 | 691 | Tileset clone(const Tileset &ts) 692 | { 693 | Tileset nts; 694 | nts.asset = ts.asset; 695 | nts.properties = ts.properties; 696 | nts.geometricError = ts.geometricError; 697 | nts.root = clone(*ts.root); 698 | nts.extensionsUsed = ts.extensionsUsed; 699 | nts.extensionsRequired = ts.extensionsRequired; 700 | return nts; 701 | } 702 | 703 | TilesetWithUri::list split(Tileset &tileset, std::size_t tileLimit) 704 | { 705 | class Splitter { 706 | public: 707 | Splitter(Tileset &tileset, std::size_t tileLimit) 708 | : tileset_(tileset), tileLimit_(tileLimit) 709 | { 710 | process(tileset_.root 711 | , boost::get_optional_value_or 712 | (tileset_.root->refine, Refinement::replace)); 713 | } 714 | 715 | TilesetWithUri::list subtrees() { return std::move(subtrees_); } 716 | 717 | private: 718 | std::size_t process(const Tile::pointer &tile, Refinement refine) 719 | { 720 | int i(0); 721 | 722 | std::size_t size(1); 723 | for (auto &child : tile->children) { 724 | path_.path.push_back(i++); 725 | 726 | auto childRefine(boost::get_optional_value_or 727 | (child->refine, refine)); 728 | 729 | auto stSize(process(child, childRefine)); 730 | if (stSize >= tileLimit_) { 731 | LOG(info2) 732 | << "Will split at <" << path_ 733 | << ">, subtree size: " << stSize; 734 | 735 | cut(child, path_, childRefine); 736 | // count in replaced child 737 | stSize = 1; 738 | } 739 | 740 | size += stSize; 741 | path_.path.pop_back(); 742 | } 743 | return size; 744 | } 745 | 746 | void cut(Tile::pointer &root, const TilePath &path 747 | , Refinement refine) 748 | { 749 | const auto uri(utility::format("tileset-%s.json", path)); 750 | 751 | subtrees_.emplace_back(uri); 752 | auto &ts(subtrees_.back().tileset); 753 | 754 | // copy common stuff (extensions etc) 755 | static_cast(ts) = static_cast(tileset_); 756 | 757 | // copy data from original tileset, set root 758 | ts.asset = tileset_.asset; 759 | ts.properties = tileset_.properties; 760 | ts.geometricError = 2 * root->geometricError; 761 | ts.root = std::move(root); 762 | ts.root->refine = refine; 763 | ts.extensionsUsed = tileset_.extensionsUsed; 764 | ts.extensionsRequired = tileset_.extensionsRequired; 765 | 766 | // regenerate root 767 | root = std::make_unique(); 768 | root->geometricError = ts.root->geometricError; 769 | root->boundingVolume = ts.root->boundingVolume; 770 | root->content = boost::in_place(); 771 | root->content->boundingVolume = ts.root->boundingVolume; 772 | root->content->uri = uri; 773 | 774 | // copy extensions and extras 775 | root->extensions = ts.root->extensions; 776 | root->extras = ts.root->extras; 777 | } 778 | 779 | Tileset &tileset_; 780 | std::size_t tileLimit_; 781 | 782 | TilePath path_; 783 | 784 | TilesetWithUri::list subtrees_; 785 | }; 786 | 787 | return Splitter(tileset, tileLimit).subtrees(); 788 | } 789 | 790 | namespace { 791 | 792 | bool inside(const Box &inner, const Box &outer) 793 | { 794 | LOG(warn2) 795 | << "Matching bounding-box bounding volumes not implemented. Yet."; 796 | return false; 797 | (void) inner; 798 | (void) outer; 799 | } 800 | 801 | bool inside(const Region &inner, const Region &outer) 802 | { 803 | return (math::inside(outer.extents, inner.extents.ll) 804 | && math::inside(outer.extents, inner.extents.ur)); 805 | } 806 | 807 | bool inside(const Sphere &inner, const Sphere &outer) 808 | { 809 | LOG(warn2) 810 | << "Matching sphere bounding volumes not implemented. Yet."; 811 | 812 | return false; 813 | (void) inner; 814 | (void) outer; 815 | } 816 | 817 | } // namespace 818 | 819 | bool inside(const BoundingVolume &inner, const BoundingVolume &outer) 820 | { 821 | if (inner.which() != outer.which()) { 822 | LOG(warn2) 823 | << "Matching different bounding volume types " 824 | "not implemented. Yet."; 825 | return false; 826 | } 827 | 828 | struct Matcher : public boost::static_visitor { 829 | const BoundingVolume &outer; 830 | 831 | Matcher(const BoundingVolume &outer) : outer(outer) { } 832 | 833 | bool operator()(const Box &b) const { 834 | return inside(b, boost::get(outer)); 835 | } 836 | 837 | bool operator()(const Region &r) const { 838 | return inside(r, boost::get(outer)); 839 | } 840 | 841 | bool operator()(const Sphere &s) const { 842 | return inside(s, boost::get(outer)); 843 | } 844 | 845 | bool operator()(const boost::blank&) { 846 | return true; 847 | } 848 | } matcher(outer); 849 | return boost::apply_visitor(matcher, inner); 850 | } 851 | 852 | std::ostream& operator<<(std::ostream &os, const Box &b) 853 | { 854 | return os << "Box[center=" << b.center 855 | << ", x=" << b.x 856 | << ", y=" << b.y 857 | << ", z=" << b.z 858 | << "]"; 859 | } 860 | 861 | std::ostream& operator<<(std::ostream &os, const Region &r) 862 | { 863 | return os << "Region[" << r.extents << "]"; 864 | } 865 | 866 | std::ostream& operator<<(std::ostream &os, const Sphere &s) 867 | { 868 | return os << "Sphere[center=" << s.center 869 | << ", radius=" << s.radius 870 | << "]"; 871 | } 872 | 873 | std::ostream& operator<<(std::ostream &os, const BoundingVolume &bv) 874 | { 875 | struct Printer : public boost::static_visitor { 876 | std::ostream &os; 877 | Printer(std::ostream &os) : os(os) {} 878 | 879 | void operator()(const Box &b) { 880 | os << b; 881 | } 882 | 883 | void operator()(const Region &r) { 884 | os << r; 885 | } 886 | 887 | void operator()(const Sphere &s) { 888 | os << s; 889 | } 890 | 891 | void operator()(const boost::blank&) { 892 | os << "[Invalid Bounding Volume]"; 893 | } 894 | } printer(os); 895 | boost::apply_visitor(printer, bv); 896 | return os; 897 | } 898 | 899 | namespace dump { 900 | 901 | template 902 | void traverseTiles(TileT &root, Op op) { 903 | struct Traverser { 904 | Op op; 905 | Traverser(Op op) : op(op) {} 906 | void process(TileT &tile, TilePath &path, TileT *parent = nullptr) 907 | { 908 | op(tile, path, parent); 909 | 910 | int i(0); 911 | for (const auto &child : tile.children) { 912 | path.path.push_back(i++); 913 | process(*child, path, &tile); 914 | path.path.pop_back(); 915 | } 916 | } 917 | } t(op); 918 | 919 | TilePath path; 920 | t.process(root, path); 921 | } 922 | 923 | bool checkExtents(const Tile &tile, const Tile *parent) 924 | { 925 | if (!parent) { return true; } 926 | 927 | return inside(tile.boundingVolume, parent->boundingVolume); 928 | } 929 | 930 | } // namespace dump 931 | 932 | void dumpMetadata(std::ostream &os, const Tileset &ts) 933 | { 934 | dump::traverseTiles(*ts.root, [&os](const Tile &tile, const TilePath &path 935 | , const Tile *parent) 936 | { 937 | os 938 | << path.depth() << "\t" 939 | << utility::join(path.path, "-", "root") << "\textents: " 940 | << tile.boundingVolume; 941 | 942 | if (!dump::checkExtents(tile, parent)) { 943 | os << " out-of-bounds!"; 944 | } 945 | 946 | if (tile.content) { 947 | os << "\tcontent: " << tile.content->uri; 948 | } else { 949 | os << "\tno content"; 950 | } 951 | 952 | os << "\n"; 953 | }); 954 | } 955 | 956 | } // namespace threedtiles 957 | --------------------------------------------------------------------------------