From e2f2bc362014be94ef8715b455c275f1c3a374a1 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 11 Nov 2020 18:51:36 -0500 Subject: [PATCH 01/21] fix compiler bug for MSVC --- CDBTo3DTiles/CMakeLists.txt | 2 +- CDBTo3DTiles/src/CDB.cpp | 8 ++++---- CDBTo3DTiles/src/CDBGeometryVectors.cpp | 2 +- CDBTo3DTiles/src/CDBModels.cpp | 15 ++++++++------- CDBTo3DTiles/src/CDBTo3DTiles.cpp | 16 +++++++++------- CDBTo3DTiles/src/TileFormatIO.cpp | 2 +- Core/CMakeLists.txt | 2 +- Core/include/{Math.h => MathUtility.h} | 0 Core/src/Ellipsoid.cpp | 2 +- Core/src/GlobeRectangle.cpp | 2 +- Core/src/IntersectionTests.cpp | 2 +- Core/src/{Math.cpp => MathUtility.cpp} | 2 +- Core/src/Transforms.cpp | 2 +- 13 files changed, 30 insertions(+), 27 deletions(-) rename Core/include/{Math.h => MathUtility.h} (100%) rename Core/src/{Math.cpp => MathUtility.cpp} (97%) diff --git a/CDBTo3DTiles/CMakeLists.txt b/CDBTo3DTiles/CMakeLists.txt index 8ac9b1e..d3acb0a 100644 --- a/CDBTo3DTiles/CMakeLists.txt +++ b/CDBTo3DTiles/CMakeLists.txt @@ -1,6 +1,6 @@ project(CDBTo3DTiles) -find_package(GDAL 3.0.4 REQUIRED) +find_package(GDAL 2.4.1 REQUIRED) add_library(CDBTo3DTiles src/Scene.cpp diff --git a/CDBTo3DTiles/src/CDB.cpp b/CDBTo3DTiles/src/CDB.cpp index 822356e..a6e5445 100644 --- a/CDBTo3DTiles/src/CDB.cpp +++ b/CDBTo3DTiles/src/CDB.cpp @@ -195,7 +195,7 @@ void CDB::traverseModelsAttributes(const CDBTile *root, const auto &featureFile = root->getCustomContentURI(); if (featureFile) { GDALDatasetUniquePtr attributesDataset = GDALDatasetUniquePtr( - (GDALDataset *) GDALOpenEx(featureFile->c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr)); + (GDALDataset *) GDALOpenEx(featureFile->string().c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr)); if (attributesDataset) { CDBModelsAttributes model(std::move(attributesDataset), *root, m_path); @@ -228,7 +228,7 @@ void CDB::traverseModelsAttributes(const CDBTile *root, auto elevationFile = m_path / (parentElevation->getRelativePath().string() + ".tif"); elevationData = GDALDatasetUniquePtr( - (GDALDataset *) GDALOpen(elevationFile.c_str(), GDALAccess::GA_ReadOnly)); + (GDALDataset *) GDALOpen(elevationFile.string().c_str(), GDALAccess::GA_ReadOnly)); if (elevationData) { for (auto &point : model.getCartographicPositions()) { @@ -365,7 +365,7 @@ void CDB::clampPointsOnElevationTileset(std::vector &points, const auto &elevationTile = elevation.first; auto elevationFile = m_path / (elevationTile.getRelativePath().string() + ".tif"); GDALDatasetUniquePtr rasterData = GDALDatasetUniquePtr( - (GDALDataset *) GDALOpen(elevationFile.c_str(), GDALAccess::GA_ReadOnly)); + (GDALDataset *) GDALOpen(elevationFile.string().c_str(), GDALAccess::GA_ReadOnly)); if (rasterData == nullptr) { continue; @@ -455,7 +455,7 @@ std::optional CDB::getImagery(const CDBTile &tile) const } auto imageryDataset = GDALDatasetUniquePtr( - (GDALDataset *) GDALOpen(imageryPath.c_str(), GDALAccess::GA_ReadOnly)); + (GDALDataset *) GDALOpen(imageryPath.string().c_str(), GDALAccess::GA_ReadOnly)); if (!imageryDataset) { return std::nullopt; diff --git a/CDBTo3DTiles/src/CDBGeometryVectors.cpp b/CDBTo3DTiles/src/CDBGeometryVectors.cpp index 8a48b38..ca1d4c4 100644 --- a/CDBTo3DTiles/src/CDBGeometryVectors.cpp +++ b/CDBTo3DTiles/src/CDBGeometryVectors.cpp @@ -51,7 +51,7 @@ std::optional CDBGeometryVectors::createFromFile(const std:: || CS_2 == static_cast(CDBVectorCS2::LinealFeature) || CS_2 == static_cast(CDBVectorCS2::PolygonFeature)) { GDALDatasetUniquePtr dataset = GDALDatasetUniquePtr( - (GDALDataset *) GDALOpenEx(file.c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr)); + (GDALDataset *) GDALOpenEx(file.string().c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr)); if (dataset) { auto geometryVector = CDBGeometryVectors(std::move(dataset), *tile, CDBPath); diff --git a/CDBTo3DTiles/src/CDBModels.cpp b/CDBTo3DTiles/src/CDBModels.cpp index dec703d..09bc4a8 100644 --- a/CDBTo3DTiles/src/CDBModels.cpp +++ b/CDBTo3DTiles/src/CDBModels.cpp @@ -1,7 +1,7 @@ #include "CDBModels.h" #include "CDB.h" #include "Ellipsoid.h" -#include "Math.h" +#include "MathUtility.h" #include "glm/glm.hpp" #include "glm/gtc/epsilon.hpp" #include "glm/gtc/matrix_transform.hpp" @@ -236,7 +236,7 @@ void CDBModel3DResult::processStateSet() osg::Image *img = tex->getImage(0); if ((img) && (!img->getFileName().empty())) { std::filesystem::path texturePath = img->getFileName(); - texture.uri = texturePath.stem().replace_extension(".png"); + texture.uri = texturePath.stem().replace_extension(".png").string(); m_images.emplace_back(img); m_textures.emplace_back(texture); material.texture = static_cast(m_textures.size() - 1); @@ -363,8 +363,9 @@ const CDBModel3DResult *CDBGTModelCache::locateModel3D(const std::string &FACC, for (std::filesystem::directory_entry featureCodeDir : std::filesystem::directory_iterator(B_Subcartegory)) { if (featureCodeDir.path().filename().string().substr(0, 3) == FACC.substr(2, 3)) { - osg::ref_ptr geometry = osgDB::readRefNodeFile(featureCodeDir.path() - / (key + ".flt")); + auto geometryFile = featureCodeDir.path() + / (key + ".flt"); + osg::ref_ptr geometry = osgDB::readRefNodeFile(geometryFile.string()); if (geometry) { CDBModel3DResult model3D; geometry->accept(model3D); @@ -544,7 +545,7 @@ std::optional CDBGSModels::createFromModelsAttributes(CDBModelsAttr // set relative path for GSModel osg::ref_ptr options = new osgDB::Options(); options->setObjectCacheHint(osgDB::Options::CACHE_NONE); - options->getDatabasePathList().push_front(GSModelZip.parent_path()); + options->getDatabasePathList().push_front(GSModelZip.parent_path().string()); // find GSModelTexture zip file to search for texture CDBTile GSModelTextureTile = CDBTile(attributeTile.getGeoCell(), @@ -558,7 +559,7 @@ std::optional CDBGSModels::createFromModelsAttributes(CDBModelsAttr std::filesystem::path GSModelTextureRelPath = GSModelTextureTile.getRelativePath(); std::string GSModelTextureTileName = GSModelTextureRelPath.stem().string(); std::filesystem::path GSModelTextureZip = CDBPath / (GSModelTextureRelPath.string() + ".zip"); - osgDB::ReaderWriter::ReadResult GSModelTextureRead = rw->openArchive(GSModelTextureZip, + osgDB::ReaderWriter::ReadResult GSModelTextureRead = rw->openArchive(GSModelTextureZip.string(), osgDB::Archive::READ); if (GSModelTextureRead.validArchive()) { osg::ref_ptr GSModelTextureArchive = GSModelTextureRead.takeArchive(); @@ -569,7 +570,7 @@ std::optional CDBGSModels::createFromModelsAttributes(CDBModelsAttr } // read GSModel geometry - osgDB::ReaderWriter::ReadResult GSModelRead = rw->openArchive(GSModelZip, osgDB::Archive::READ); + osgDB::ReaderWriter::ReadResult GSModelRead = rw->openArchive(GSModelZip.string(), osgDB::Archive::READ); if (GSModelRead.validArchive()) { osg::ref_ptr archive = GSModelRead.takeArchive(); return CDBGSModels(std::move(attributes), modelTile, *archive, *options); diff --git a/CDBTo3DTiles/src/CDBTo3DTiles.cpp b/CDBTo3DTiles/src/CDBTo3DTiles.cpp index af88464..b5bf52c 100644 --- a/CDBTo3DTiles/src/CDBTo3DTiles.cpp +++ b/CDBTo3DTiles/src/CDBTo3DTiles.cpp @@ -1,7 +1,7 @@ #include "CDBTo3DTiles.h" #include "CDB.h" #include "Gltf.h" -#include "Math.h" +#include "MathUtility.h" #include "TileFormatIO.h" #include "cpl_conv.h" #include "gdal.h" @@ -430,7 +430,7 @@ Texture Converter::Impl::createImageryTexture(CDBImagery &imagery, } Texture texture; - texture.uri = textureRelativePath; + texture.uri = textureRelativePath.string(); texture.magFilter = TextureFilter::LINEAR; texture.minFilter = TextureFilter::LINEAR_MIPMAP_NEAREST; @@ -492,7 +492,9 @@ void Converter::Impl::addGTModelToTilesetCollection(const CDBGTModels &model, // write to glb tinygltf::TinyGLTF loader; std::filesystem::path modelGltfURI = MODEL_GLTF_SUB_DIR / (modelKey + ".glb"); - loader.WriteGltfSceneToFile(&gltf, tilesetDirectory / modelGltfURI, false, false, false, true); + auto modelGltfAbsolutePath = tilesetDirectory + / modelGltfURI; + loader.WriteGltfSceneToFile(&gltf, modelGltfAbsolutePath.string(), false, false, false, true); GTModelsToGltf.insert({modelKey, modelGltfURI}); } @@ -510,7 +512,7 @@ void Converter::Impl::addGTModelToTilesetCollection(const CDBGTModels &model, writeToCMPT(static_cast(instances.size()), fs, [&](std::ofstream &os, size_t) { const auto &GltfURI = GTModelsToGltf[instance->first]; const auto &instanceIndices = instance->second; - size_t totalWrite = writeToI3DM(GltfURI, modelsAttribs, instanceIndices, os); + size_t totalWrite = writeToI3DM(GltfURI.string(), modelsAttribs, instanceIndices, os); instance = std::next(instance); return totalWrite; }); @@ -555,9 +557,9 @@ std::vector Converter::Impl::writeModeTextures(const std::vectorstring(); } for (auto child : tile.getChildren()) { diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index eb5d2da..9dbe54a 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -1,7 +1,7 @@ project(Core) set(sources - "src/Math.cpp" + "src/MathUtility.cpp" "src/Transforms.cpp" "src/IntersectionTests.cpp" "src/EllipsoidTangentPlane.cpp" diff --git a/Core/include/Math.h b/Core/include/MathUtility.h similarity index 100% rename from Core/include/Math.h rename to Core/include/MathUtility.h diff --git a/Core/src/Ellipsoid.cpp b/Core/src/Ellipsoid.cpp index 5154b13..e924ff2 100644 --- a/Core/src/Ellipsoid.cpp +++ b/Core/src/Ellipsoid.cpp @@ -1,5 +1,5 @@ #include "Ellipsoid.h" -#include "Math.h" +#include "MathUtility.h" namespace Core { diff --git a/Core/src/GlobeRectangle.cpp b/Core/src/GlobeRectangle.cpp index d9e66cd..112a40d 100644 --- a/Core/src/GlobeRectangle.cpp +++ b/Core/src/GlobeRectangle.cpp @@ -1,5 +1,5 @@ #include "GlobeRectangle.h" -#include "Math.h" +#include "MathUtility.h" namespace Core { diff --git a/Core/src/IntersectionTests.cpp b/Core/src/IntersectionTests.cpp index 70daad4..f4ccb59 100644 --- a/Core/src/IntersectionTests.cpp +++ b/Core/src/IntersectionTests.cpp @@ -1,5 +1,5 @@ #include "IntersectionTests.h" -#include "Math.h" +#include "MathUtility.h" namespace Core { diff --git a/Core/src/Math.cpp b/Core/src/MathUtility.cpp similarity index 97% rename from Core/src/Math.cpp rename to Core/src/MathUtility.cpp index e15537c..363ed28 100644 --- a/Core/src/Math.cpp +++ b/Core/src/MathUtility.cpp @@ -1,4 +1,4 @@ -#include "Math.h" +#include "MathUtility.h" namespace Core { const double Math::EPSILON1 = 1e-1; diff --git a/Core/src/Transforms.cpp b/Core/src/Transforms.cpp index 2352c76..528c30b 100644 --- a/Core/src/Transforms.cpp +++ b/Core/src/Transforms.cpp @@ -1,5 +1,5 @@ #include "Transforms.h" -#include "Math.h" +#include "MathUtility.h" namespace Core { glm::dmat4x4 Transforms::eastNorthUpToFixedFrame(const glm::dvec3 &origin, const Ellipsoid &ellipsoid) From c6d3ffeb99c16930ce6617f922955fd933c6ac01 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 11 Nov 2020 18:54:11 -0500 Subject: [PATCH 02/21] fix filesystem to string --- CDBTo3DTiles/src/CDBAttributes.cpp | 2 +- CDBTo3DTiles/src/CDBGeometryVectors.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CDBTo3DTiles/src/CDBAttributes.cpp b/CDBTo3DTiles/src/CDBAttributes.cpp index e039c08..14abc05 100644 --- a/CDBTo3DTiles/src/CDBAttributes.cpp +++ b/CDBTo3DTiles/src/CDBAttributes.cpp @@ -189,7 +189,7 @@ std::optional CDBModelsAttributes::createClassesAttributes } GDALDatasetUniquePtr dataset = GDALDatasetUniquePtr( - (GDALDataset *) GDALOpenEx(classLevelPath.c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr)); + (GDALDataset *) GDALOpenEx(classLevelPath.string().c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr)); if (!dataset) { return std::nullopt; } diff --git a/CDBTo3DTiles/src/CDBGeometryVectors.cpp b/CDBTo3DTiles/src/CDBGeometryVectors.cpp index ca1d4c4..f599eaf 100644 --- a/CDBTo3DTiles/src/CDBGeometryVectors.cpp +++ b/CDBTo3DTiles/src/CDBGeometryVectors.cpp @@ -242,7 +242,7 @@ std::optional createClassesAttributes(const CDBTile &insta } GDALDatasetUniquePtr dataset = GDALDatasetUniquePtr( - (GDALDataset *) GDALOpenEx(classLevelPath.c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr)); + (GDALDataset *) GDALOpenEx(classLevelPath.string().c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr)); if (!dataset) { return std::nullopt; } From 441436bff2e273654746e3d5efec95ece22babaf Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 12 Nov 2020 10:17:35 -0500 Subject: [PATCH 03/21] fix MSVC compiler error in Tests --- Tests/CDBElevationTest.cpp | 4 ++-- Tests/CDBGSModelsTest.cpp | 6 +++--- Tests/CDBGTModelsTest.cpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Tests/CDBElevationTest.cpp b/Tests/CDBElevationTest.cpp index 4c8eb03..13d091b 100644 --- a/Tests/CDBElevationTest.cpp +++ b/Tests/CDBElevationTest.cpp @@ -42,7 +42,7 @@ static void checkElevationDuplicated(const std::filesystem::path &imageryPath, continue; } - auto imageryTile = CDBTile::createFromFile(tilePath.path().stem()); + auto imageryTile = CDBTile::createFromFile(tilePath.path().stem().string()); auto elevationTile = CDBTile(imageryTile->getGeoCell(), CDBDataset::Elevation, 1, @@ -486,7 +486,7 @@ TEST_CASE("Test conversion using elevation LOD only", "[CDBElevationConversion]" // elevation max level is 1, so we check that int maxLevel = -10; for (std::filesystem::directory_entry entry : std::filesystem::directory_iterator(elevationOutputDir)) { - auto tile = CDBTile::createFromFile(entry.path().filename()); + auto tile = CDBTile::createFromFile(entry.path().filename().string()); if (tile) { maxLevel = glm::max(tile->getLevel(), maxLevel); } diff --git a/Tests/CDBGSModelsTest.cpp b/Tests/CDBGSModelsTest.cpp index 9944d8c..0b36144 100644 --- a/Tests/CDBGSModelsTest.cpp +++ b/Tests/CDBGSModelsTest.cpp @@ -16,7 +16,7 @@ TEST_CASE("Test creating GSModel from model attributes", "[CDBGSModels]") // read in the GSFeature data GDALDatasetUniquePtr attributesDataset = GDALDatasetUniquePtr( - (GDALDataset *) GDALOpenEx(input.c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr)); + (GDALDataset *) GDALOpenEx(input.string().c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr)); REQUIRE(attributesDataset != nullptr); auto GSFeatureTile = CDBTile::createFromFile(input.filename().string()); @@ -62,7 +62,7 @@ TEST_CASE("Test creating GSModel from model attributes", "[CDBGSModels]") // read in the GSFeature data GDALDatasetUniquePtr attributesDataset = GDALDatasetUniquePtr( - (GDALDataset *) GDALOpenEx(input.c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr)); + (GDALDataset *) GDALOpenEx(input.string().c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr)); REQUIRE(attributesDataset != nullptr); auto GSFeatureTile = CDBTile::createFromFile(input.filename().string()); @@ -128,7 +128,7 @@ TEST_CASE("Test converting GSModel to tileset.json", "[CDBGSModels]") std::filesystem::directory_iterator(GSModelGeometryInput)) { for (std::filesystem::directory_entry UREFDir : std::filesystem::directory_iterator(levelDir)) { for (std::filesystem::directory_entry tilePath : std::filesystem::directory_iterator(UREFDir)) { - auto GSModelGeometryTile = CDBTile::createFromFile(tilePath.path().stem()); + auto GSModelGeometryTile = CDBTile::createFromFile(tilePath.path().stem().string()); REQUIRE(std::filesystem::exists( tilesetPath / (GSModelGeometryTile->getRelativePath().stem().string() + ".b3dm"))); ++geometryModelCount; diff --git a/Tests/CDBGTModelsTest.cpp b/Tests/CDBGTModelsTest.cpp index e9d6054..b27079d 100644 --- a/Tests/CDBGTModelsTest.cpp +++ b/Tests/CDBGTModelsTest.cpp @@ -18,7 +18,7 @@ static void checkGTTilesetDirectoryStructure(const std::filesystem::path &tilese REQUIRE(std::filesystem::exists(tilesetOutput / "Gltf" / "Textures")); size_t GTFeatureCount = 0; for (std::filesystem::directory_entry entry : std::filesystem::directory_iterator(tilesetOutput)) { - auto tile = CDBTile::createFromFile(entry.path().filename()); + auto tile = CDBTile::createFromFile(entry.path().filename().string()); if (tile) { REQUIRE(std::filesystem::exists(CDBPath / (tile->getRelativePath().string() + ".dbf"))); ++GTFeatureCount; @@ -72,7 +72,7 @@ TEST_CASE("Test locating GTModel with metadata in CDB database", "[CDBGTModels]" // read in the GTFeature data GDALDatasetUniquePtr attributesDataset = GDALDatasetUniquePtr( - (GDALDataset *) GDALOpenEx(input.c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr)); + (GDALDataset *) GDALOpenEx(input.string().c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr)); REQUIRE(attributesDataset != nullptr); auto GTFeatureTile = CDBTile::createFromFile(input.filename().string()); From b9001da1a3974e3d816f6edde4a998dd3f0bc00b Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 12 Nov 2020 10:25:05 -0500 Subject: [PATCH 04/21] add MSVC definition flags to prevent warning about deprecated CPP 17 --- CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3870da6..f71eeb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,12 @@ enable_testing() function(configure_project target_name) if (MSVC) - target_compile_options(${target_name} PRIVATE /W4 /WX /wd4201) + target_compile_options(${target_name} PRIVATE + /W4 + /WX + /wd4201 + /D_CRT_SECURE_NO_WARNINGS + /D_SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING) else() target_compile_options(${target_name} PRIVATE -Werror -Wall -Wextra -Wconversion -Wpedantic -Wshadow) endif() From dbaac6aa8822256a42299345a0f03fe12ab5d53f Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 12 Nov 2020 10:51:02 -0500 Subject: [PATCH 05/21] use ref to traverse loop --- CDBTo3DTiles/src/CDBGeometryVectors.cpp | 6 +++--- CDBTo3DTiles/src/CDBModels.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CDBTo3DTiles/src/CDBGeometryVectors.cpp b/CDBTo3DTiles/src/CDBGeometryVectors.cpp index f599eaf..b09ffed 100644 --- a/CDBTo3DTiles/src/CDBGeometryVectors.cpp +++ b/CDBTo3DTiles/src/CDBGeometryVectors.cpp @@ -93,7 +93,7 @@ void CDBGeometryVectors::createPoint(GDALDataset *vectorDataset) auto center = m_mesh.aabb->center(); m_mesh.positionRTCs.reserve(m_mesh.positions.size()); - for (auto position : m_mesh.positions) { + for (const auto &position : m_mesh.positions) { m_mesh.positionRTCs.emplace_back(position - center); } } @@ -139,7 +139,7 @@ void CDBGeometryVectors::createPolyline(GDALDataset *vectorDataset) auto center = m_mesh.aabb->center(); m_mesh.positionRTCs.reserve(m_mesh.positions.size()); - for (auto position : m_mesh.positions) { + for (const auto &position : m_mesh.positions) { m_mesh.positionRTCs.emplace_back(position - center); } } @@ -177,7 +177,7 @@ void CDBGeometryVectors::createPolygonOrMultiPolygon(GDALDataset *vectorDataset) auto center = m_mesh.aabb->center(); m_mesh.positionRTCs.reserve(m_mesh.positions.size()); - for (auto position : m_mesh.positions) { + for (const auto &position : m_mesh.positions) { m_mesh.positionRTCs.emplace_back(position - center); } } diff --git a/CDBTo3DTiles/src/CDBModels.cpp b/CDBTo3DTiles/src/CDBModels.cpp index 09bc4a8..9ebdf9a 100644 --- a/CDBTo3DTiles/src/CDBModels.cpp +++ b/CDBTo3DTiles/src/CDBModels.cpp @@ -147,8 +147,8 @@ void CDBModel3DResult::finalize() for (auto &mesh : m_meshes) { mesh.positionRTCs.reserve(mesh.positions.size()); auto center = mesh.aabb->center(); - for (auto pos : mesh.positions) { - mesh.positionRTCs.emplace_back(pos - center); + for (const auto &position : mesh.positions) { + mesh.positionRTCs.emplace_back(position - center); } } } From 914b03ce1a9b54ffc90e2b110353478f16862990 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 12 Nov 2020 11:09:01 -0500 Subject: [PATCH 06/21] remove jpeg dependency for OSG --- CDBTo3DTiles/CMakeLists.txt | 1 - CDBTo3DTiles/src/CDBTo3DTiles.cpp | 1 - extern/CMakeLists.txt | 1 - 3 files changed, 3 deletions(-) diff --git a/CDBTo3DTiles/CMakeLists.txt b/CDBTo3DTiles/CMakeLists.txt index d3acb0a..8c1535b 100644 --- a/CDBTo3DTiles/CMakeLists.txt +++ b/CDBTo3DTiles/CMakeLists.txt @@ -43,7 +43,6 @@ target_link_libraries(CDBTo3DTiles osgdb_openflight osgdb_rgb osgdb_png - osgdb_jpeg osgdb_zip osgDB osg diff --git a/CDBTo3DTiles/src/CDBTo3DTiles.cpp b/CDBTo3DTiles/src/CDBTo3DTiles.cpp index b5bf52c..ab4dcee 100644 --- a/CDBTo3DTiles/src/CDBTo3DTiles.cpp +++ b/CDBTo3DTiles/src/CDBTo3DTiles.cpp @@ -712,7 +712,6 @@ void Converter::convert() } USE_OSGPLUGIN(png) -USE_OSGPLUGIN(jpeg) USE_OSGPLUGIN(zip) USE_OSGPLUGIN(rgb) USE_OSGPLUGIN(OpenFlight) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 8654dec..cc7f9c2 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -13,7 +13,6 @@ set(BUILD_OSG_DEPRECATED_SERIALIZERS OFF CACHE BOOL "Build OSG Applications") set(BUILD_OSG_PLUGINS_BY_DEFAULT OFF CACHE BOOL "Build All default plugin") set(BUILD_OSG_PLUGIN_OPENFLIGHT 1) set(BUILD_OSG_PLUGIN_PNG 1) -set(BUILD_OSG_PLUGIN_JPEG 1) set(BUILD_OSG_PLUGIN_ZIP 1) set(BUILD_OSG_PLUGIN_RGB 1) add_subdirectory(OpenSceneGraph) From 6edae0625b6820dafc903bfe02b1b1a68029db3a Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 12 Nov 2020 11:10:13 -0500 Subject: [PATCH 07/21] add visual studio config to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 8eec003..d3b05cc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ *.user *.vscode +*.vs +CMakeSettings.json Build build From 922a9b2731336310d7f8d405873f74af2730a422 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 12 Nov 2020 11:34:56 -0500 Subject: [PATCH 08/21] disable cxxopts examples and tests --- extern/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index cc7f9c2..e6faef6 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -2,6 +2,8 @@ project(ThirdParty) add_subdirectory(meshoptimizer) +set(CXXOPTS_BUILD_EXAMPLES OFF CACHE BOOL "Build cxxopts examples") +set(CXXOPTS_BUILD_TESTS OFF CACHE BOOL "Build cxxopts tests") add_subdirectory(cxxopts) add_subdirectory(Catch2) From 7788db68cbc2029522c1943baa64e84d2f108915 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 12 Nov 2020 16:51:44 -0500 Subject: [PATCH 09/21] add OSG_LIBRARY_STATIC to make MSVC build --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f71eeb3..bdc6a60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ function(configure_project target_name) /W4 /WX /wd4201 + /DOSG_LIBRARY_STATIC /D_CRT_SECURE_NO_WARNINGS /D_SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING) else() From 83dc6f604069c8c4da31eaea016ad264b2561006 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 12 Nov 2020 16:52:34 -0500 Subject: [PATCH 10/21] add out directory to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d3b05cc..16730cd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ CMakeSettings.json Build build +out From 9c8b8adfe5d815770fadb60db4df0c178ba10a2a Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 16 Nov 2020 15:49:07 -0500 Subject: [PATCH 11/21] change gdal setting in cmake --- CDBTo3DTiles/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CDBTo3DTiles/CMakeLists.txt b/CDBTo3DTiles/CMakeLists.txt index 8c1535b..42d158d 100644 --- a/CDBTo3DTiles/CMakeLists.txt +++ b/CDBTo3DTiles/CMakeLists.txt @@ -26,7 +26,7 @@ set(PRIVATE_THIRD_PARTY_INCLUDE_PATHS ${osg_INCLUDE_DIRS} ${earcut_INCLUDE_DIRS} ${nlohmann_json_INCLUDE_DIRS} - ${GDAL_INCLUDE_DIRS} + ${GDAL_INCLUDE_DIR} ${tinygltf_INCLUDE_DIRS} ) @@ -49,7 +49,7 @@ target_link_libraries(CDBTo3DTiles OpenThreads meshoptimizer Core - ${GDAL_LIBRARIES}) + ${GDAL_LIBRARY}) set_property(TARGET CDBTo3DTiles PROPERTY From 2207f881b3c1789b491251a7b6953c978dfeeaf9 Mon Sep 17 00:00:00 2001 From: Bao Tran Thien Date: Sat, 28 Nov 2020 21:50:53 -0500 Subject: [PATCH 12/21] make sure all test data files are closed before removing output directory --- Tests/CDBElevationTest.cpp | 160 ++++++++++++++++++++----------------- Tests/CDBGSModelsTest.cpp | 55 +++++++------ 2 files changed, 114 insertions(+), 101 deletions(-) diff --git a/Tests/CDBElevationTest.cpp b/Tests/CDBElevationTest.cpp index 13d091b..b0c0f79 100644 --- a/Tests/CDBElevationTest.cpp +++ b/Tests/CDBElevationTest.cpp @@ -349,25 +349,27 @@ TEST_CASE("Test conversion when elevation has more LOD than imagery", "[CDBEleva std::filesystem::path output = "ElevationMoreLODNegativeImagery"; std::filesystem::path elevationOutputDir = output / "Tiles" / "N32" / "W118" / "Elevation" / "1_1"; - Converter converter(input, output); - converter.convert(); + { + Converter converter(input, output); + converter.convert(); - // check that all the imagery created as textures - std::filesystem::path textureOutputDir = elevationOutputDir / "Textures"; - REQUIRE(std::filesystem::exists(textureOutputDir)); - checkAllConvertedImagery(input / "Tiles" / "N32" / "W118" / "004_Imagery", textureOutputDir, 10); + // check that all the imagery created as textures + std::filesystem::path textureOutputDir = elevationOutputDir / "Textures"; + REQUIRE(std::filesystem::exists(textureOutputDir)); + checkAllConvertedImagery(input / "Tiles" / "N32" / "W118" / "004_Imagery", textureOutputDir, 10); - // verified tileset.json - std::filesystem::path tilesetPath = elevationOutputDir / "tileset.json"; - REQUIRE(std::filesystem::exists(tilesetPath)); + // verified tileset.json + std::filesystem::path tilesetPath = elevationOutputDir / "tileset.json"; + REQUIRE(std::filesystem::exists(tilesetPath)); - std::ifstream verifiedJS(input / "VerifiedTileset.json"); - nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); + std::ifstream verifiedJS(input / "VerifiedTileset.json"); + nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); - std::ifstream testJS(tilesetPath); - nlohmann::json testJson = nlohmann::json::parse(testJS); + std::ifstream testJS(tilesetPath); + nlohmann::json testJson = nlohmann::json::parse(testJS); - REQUIRE(testJson == verifiedJson); + REQUIRE(testJson == verifiedJson); + } // remove the test output std::filesystem::remove_all(output); @@ -379,25 +381,27 @@ TEST_CASE("Test conversion when elevation has more LOD than imagery", "[CDBEleva std::filesystem::path output = "ElevationMoreLODPositiveImagery"; std::filesystem::path elevationOutputDir = output / "Tiles" / "N32" / "W118" / "Elevation" / "1_1"; - Converter converter(input, output); - converter.convert(); + { + Converter converter(input, output); + converter.convert(); - // check all the imagery are created - std::filesystem::path textureOutputDir = elevationOutputDir / "Textures"; - REQUIRE(std::filesystem::exists(textureOutputDir)); - checkAllConvertedImagery(input / "Tiles" / "N32" / "W118" / "004_Imagery", textureOutputDir, 13); + // check all the imagery are created + std::filesystem::path textureOutputDir = elevationOutputDir / "Textures"; + REQUIRE(std::filesystem::exists(textureOutputDir)); + checkAllConvertedImagery(input / "Tiles" / "N32" / "W118" / "004_Imagery", textureOutputDir, 13); - // verified tileset.json - std::filesystem::path tilesetPath = elevationOutputDir / "tileset.json"; - REQUIRE(std::filesystem::exists(tilesetPath)); + // verified tileset.json + std::filesystem::path tilesetPath = elevationOutputDir / "tileset.json"; + REQUIRE(std::filesystem::exists(tilesetPath)); - std::ifstream verifiedJS(input / "VerifiedTileset.json"); - nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); + std::ifstream verifiedJS(input / "VerifiedTileset.json"); + nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); - std::ifstream testJS(tilesetPath); - nlohmann::json testJson = nlohmann::json::parse(testJS); + std::ifstream testJS(tilesetPath); + nlohmann::json testJson = nlohmann::json::parse(testJS); - REQUIRE(testJson == verifiedJson); + REQUIRE(testJson == verifiedJson); + } // remove the test output std::filesystem::remove_all(output); @@ -412,28 +416,30 @@ TEST_CASE("Test conversion when imagery has more LOD than elevation", "[CDBEleva std::filesystem::path output = "ImageryMoreLODNegativeElevation"; std::filesystem::path elevationOutputDir = output / "Tiles" / "N32" / "W118" / "Elevation" / "1_1"; - Converter converter(input, output); - converter.convert(); + { + Converter converter(input, output); + converter.convert(); - // check all the imagery are created. - // check that elevation is duplicated to levels where imagery exists - std::filesystem::path imageryInput = input / "Tiles" / "N32" / "W118" / "004_Imagery"; - std::filesystem::path textureOutputDir = elevationOutputDir / "Textures"; - REQUIRE(std::filesystem::exists(textureOutputDir)); - checkAllConvertedImagery(imageryInput, textureOutputDir, 13); - checkElevationDuplicated(imageryInput, elevationOutputDir, 13); + // check all the imagery are created. + // check that elevation is duplicated to levels where imagery exists + std::filesystem::path imageryInput = input / "Tiles" / "N32" / "W118" / "004_Imagery"; + std::filesystem::path textureOutputDir = elevationOutputDir / "Textures"; + REQUIRE(std::filesystem::exists(textureOutputDir)); + checkAllConvertedImagery(imageryInput, textureOutputDir, 13); + checkElevationDuplicated(imageryInput, elevationOutputDir, 13); - // verified tileset.json - std::filesystem::path tilesetPath = elevationOutputDir / "tileset.json"; - REQUIRE(std::filesystem::exists(tilesetPath)); + // verified tileset.json + std::filesystem::path tilesetPath = elevationOutputDir / "tileset.json"; + REQUIRE(std::filesystem::exists(tilesetPath)); - std::ifstream verifiedJS(input / "VerifiedTileset.json"); - nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); + std::ifstream verifiedJS(input / "VerifiedTileset.json"); + nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); - std::ifstream testJS(tilesetPath); - nlohmann::json testJson = nlohmann::json::parse(testJS); + std::ifstream testJS(tilesetPath); + nlohmann::json testJson = nlohmann::json::parse(testJS); - REQUIRE(testJson == verifiedJson); + REQUIRE(testJson == verifiedJson); + } // remove the test output std::filesystem::remove_all(output); @@ -445,28 +451,30 @@ TEST_CASE("Test conversion when imagery has more LOD than elevation", "[CDBEleva std::filesystem::path output = "ImageryMoreLODPositiveElevation"; std::filesystem::path elevationOutputDir = output / "Tiles" / "N32" / "W118" / "Elevation" / "1_1"; - Converter converter(input, output); - converter.convert(); + { + Converter converter(input, output); + converter.convert(); - // check all the imagery are created. - // check that elevation is duplicated to levels where imagery exists - std::filesystem::path imageryInput = input / "Tiles" / "N32" / "W118" / "004_Imagery"; - std::filesystem::path textureOutputDir = elevationOutputDir / "Textures"; - REQUIRE(std::filesystem::exists(textureOutputDir)); - checkAllConvertedImagery(imageryInput, textureOutputDir, 18); - checkElevationDuplicated(imageryInput, elevationOutputDir, 18); + // check all the imagery are created. + // check that elevation is duplicated to levels where imagery exists + std::filesystem::path imageryInput = input / "Tiles" / "N32" / "W118" / "004_Imagery"; + std::filesystem::path textureOutputDir = elevationOutputDir / "Textures"; + REQUIRE(std::filesystem::exists(textureOutputDir)); + checkAllConvertedImagery(imageryInput, textureOutputDir, 18); + checkElevationDuplicated(imageryInput, elevationOutputDir, 18); - // verified tileset.json - std::filesystem::path tilesetPath = elevationOutputDir / "tileset.json"; - REQUIRE(std::filesystem::exists(tilesetPath)); + // verified tileset.json + std::filesystem::path tilesetPath = elevationOutputDir / "tileset.json"; + REQUIRE(std::filesystem::exists(tilesetPath)); - std::ifstream verifiedJS(input / "VerifiedTileset.json"); - nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); + std::ifstream verifiedJS(input / "VerifiedTileset.json"); + nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); - std::ifstream testJS(tilesetPath); - nlohmann::json testJson = nlohmann::json::parse(testJS); + std::ifstream testJS(tilesetPath); + nlohmann::json testJson = nlohmann::json::parse(testJS); - REQUIRE(testJson == verifiedJson); + REQUIRE(testJson == verifiedJson); + } // remove the test output std::filesystem::remove_all(output); @@ -479,21 +487,23 @@ TEST_CASE("Test conversion using elevation LOD only", "[CDBElevationConversion]" std::filesystem::path output = "ImageryMoreLODPositiveElevation"; std::filesystem::path elevationOutputDir = output / "Tiles" / "N32" / "W118" / "Elevation" / "1_1"; - Converter converter(input, output); - converter.setElevationLODOnly(true); - converter.convert(); - - // elevation max level is 1, so we check that - int maxLevel = -10; - for (std::filesystem::directory_entry entry : std::filesystem::directory_iterator(elevationOutputDir)) { - auto tile = CDBTile::createFromFile(entry.path().filename().string()); - if (tile) { - maxLevel = glm::max(tile->getLevel(), maxLevel); - } + { + Converter converter(input, output); + converter.setElevationLODOnly(true); + converter.convert(); + + // elevation max level is 1, so we check that + int maxLevel = -10; + for (std::filesystem::directory_entry entry : std::filesystem::directory_iterator(elevationOutputDir)) { + auto tile = CDBTile::createFromFile(entry.path().filename().string()); + if (tile) { + maxLevel = glm::max(tile->getLevel(), maxLevel); + } + } + + REQUIRE(maxLevel == 1); } - REQUIRE(maxLevel == 1); - // remove the test output std::filesystem::remove_all(output); } diff --git a/Tests/CDBGSModelsTest.cpp b/Tests/CDBGSModelsTest.cpp index 0b36144..aa228ae 100644 --- a/Tests/CDBGSModelsTest.cpp +++ b/Tests/CDBGSModelsTest.cpp @@ -117,33 +117,36 @@ TEST_CASE("Test converting GSModel to tileset.json", "[CDBGSModels]") { std::filesystem::path CDBPath = dataPath / "GSModelsWithGTModelTexture"; std::filesystem::path output = "GSModelsWithGTModelTexture"; - Converter converter(CDBPath, output); - converter.convert(); - - // make sure every zip file in GSModelGeometry will have a corresponding b3dm in the output - size_t geometryModelCount = 0; - std::filesystem::path GSModelGeometryInput = CDBPath / "Tiles" / "N32" / "W118" / "300_GSModelGeometry"; - std::filesystem::path tilesetPath = output / "Tiles" / "N32" / "W118" / "GSModels" / "1_1"; - for (std::filesystem::directory_entry levelDir : - std::filesystem::directory_iterator(GSModelGeometryInput)) { - for (std::filesystem::directory_entry UREFDir : std::filesystem::directory_iterator(levelDir)) { - for (std::filesystem::directory_entry tilePath : std::filesystem::directory_iterator(UREFDir)) { - auto GSModelGeometryTile = CDBTile::createFromFile(tilePath.path().stem().string()); - REQUIRE(std::filesystem::exists( - tilesetPath / (GSModelGeometryTile->getRelativePath().stem().string() + ".b3dm"))); - ++geometryModelCount; - } - } - } - REQUIRE(geometryModelCount == 3); - - // check the tileset - std::ifstream verifiedJS(CDBPath / "VerifiedTileset.json"); - std::ifstream testJS(tilesetPath / "tileset.json"); - nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); - nlohmann::json testJson = nlohmann::json::parse(testJS); - REQUIRE(testJson == verifiedJson); + { + Converter converter(CDBPath, output); + converter.convert(); + + // make sure every zip file in GSModelGeometry will have a corresponding b3dm in the output + size_t geometryModelCount = 0; + std::filesystem::path GSModelGeometryInput = CDBPath / "Tiles" / "N32" / "W118" / "300_GSModelGeometry"; + std::filesystem::path tilesetPath = output / "Tiles" / "N32" / "W118" / "GSModels" / "1_1"; + for (std::filesystem::directory_entry levelDir : + std::filesystem::directory_iterator(GSModelGeometryInput)) { + for (std::filesystem::directory_entry UREFDir : std::filesystem::directory_iterator(levelDir)) { + for (std::filesystem::directory_entry tilePath : std::filesystem::directory_iterator(UREFDir)) { + auto GSModelGeometryTile = CDBTile::createFromFile(tilePath.path().stem().string()); + REQUIRE(std::filesystem::exists( + tilesetPath / (GSModelGeometryTile->getRelativePath().stem().string() + ".b3dm"))); + ++geometryModelCount; + } + } + } + + REQUIRE(geometryModelCount == 3); + + // check the tileset + std::ifstream verifiedJS(CDBPath / "VerifiedTileset.json"); + std::ifstream testJS(tilesetPath / "tileset.json"); + nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); + nlohmann::json testJson = nlohmann::json::parse(testJS); + REQUIRE(testJson == verifiedJson); + } // remove the test output std::filesystem::remove_all(output); From c67dd8efd5c3f4c5592b2f99a5720b907b6db1c2 Mon Sep 17 00:00:00 2001 From: Bao Tran Thien Date: Sat, 28 Nov 2020 22:01:18 -0500 Subject: [PATCH 13/21] fix simplified mesh test to make sure file close before removing output dir --- Tests/CDBElevationTest.cpp | 209 +++++++++++++++++++------------------ Tests/CDBGSModelsTest.cpp | 6 +- 2 files changed, 108 insertions(+), 107 deletions(-) diff --git a/Tests/CDBElevationTest.cpp b/Tests/CDBElevationTest.cpp index 4408dcf..d84961f 100644 --- a/Tests/CDBElevationTest.cpp +++ b/Tests/CDBElevationTest.cpp @@ -514,116 +514,117 @@ TEST_CASE("Test that elevation conversion uses uniform grid mesh instead of simp "mesh is empty", "[CDBElevationConversion]") { - float decimateError = 0.5f; - float thresholdIndices = 0.02f; - std::filesystem::path input = dataPath / "ImageryMoreLODPositiveElevation"; std::filesystem::path output = "ImageryMoreLODPositiveElevation"; std::filesystem::path elevationOutputDir = output / "Tiles" / "N32" / "W118" / "Elevation" / "1_1"; - // confirm that the elevation at LC09 is empty after decimation - std::filesystem::path LC09Path = input / "Tiles" / "N32" / "W118" / "001_Elevation" / "LC" / "U0" - / "N32W118_D001_S001_T001_LC09_U0_R0.tif"; - auto elevation = CDBElevation::createFromFile(LC09Path); - REQUIRE(elevation != std::nullopt); - size_t targetIndices = static_cast( - thresholdIndices * static_cast(elevation->getUniformGridMesh().indices.size())); - auto simplied = elevation->createSimplifiedMesh(targetIndices, decimateError); - REQUIRE(simplied.indices.size() == 0); - REQUIRE(simplied.positionRTCs.size() == 0); - REQUIRE(simplied.normals.size() == 0); - REQUIRE(simplied.UVs.size() == 0); - - // convert the CDB - Converter converter(input, output); - converter.setElevationDecimateError(decimateError); - converter.setElevationThresholdIndices(thresholdIndices); - converter.setElevationLODOnly(true); - converter.convert(); - - // check that LC09 is using uniform grid - std::ifstream fs(elevationOutputDir / "N32W118_D001_S001_T001_LC09_U0_R0.b3dm", std::ios::binary); - B3dmHeader b3dm; - fs.read(reinterpret_cast(&b3dm), sizeof(b3dm)); - - size_t glbBegin = sizeof(b3dm) + b3dm.featureTableJsonByteLength + b3dm.featureTableBinByteLength - + b3dm.batchTableJsonByteLength + b3dm.batchTableBinByteLength; - std::vector glb(b3dm.byteLength - glbBegin); - - fs.seekg(glbBegin); - fs.read(reinterpret_cast(glb.data()), glb.size()); - - tinygltf::TinyGLTF loader; - tinygltf::Model model; - std::string err; - std::string warn; - loader.LoadBinaryFromMemory(&model, - &err, - &warn, - glb.data(), - static_cast(glb.size()), - elevationOutputDir); - - REQUIRE(model.meshes.size() == 1); - - // check mesh primitive - const auto &gltfMesh = model.meshes.front(); - REQUIRE(gltfMesh.primitives.size() == 1); - - const auto &gltfPrimitive = gltfMesh.primitives.front(); - REQUIRE(gltfPrimitive.mode == TINYGLTF_MODE_TRIANGLES); - REQUIRE(gltfPrimitive.indices == 0); - REQUIRE(gltfPrimitive.attributes.at("POSITION") == 1); - REQUIRE(gltfPrimitive.attributes.at("TEXCOORD_0") == 2); - - // check accessors - const auto &uniformElevation = elevation->getUniformGridMesh(); - const auto &indicesAccessor = model.accessors[gltfPrimitive.indices]; - REQUIRE(indicesAccessor.count == uniformElevation.indices.size()); - REQUIRE(indicesAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT); - - const auto &positionAccessor = model.accessors[gltfPrimitive.attributes.at("POSITION")]; - REQUIRE(positionAccessor.count == uniformElevation.positionRTCs.size()); - REQUIRE(positionAccessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT); - REQUIRE(positionAccessor.type == TINYGLTF_TYPE_VEC3); - - const auto &texCoordAccessor = model.accessors[gltfPrimitive.attributes.at("TEXCOORD_0")]; - REQUIRE(texCoordAccessor.count == uniformElevation.UVs.size()); - REQUIRE(texCoordAccessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT); - REQUIRE(texCoordAccessor.type == TINYGLTF_TYPE_VEC2); - - // check buffer view - const auto &gltfBufferData = model.buffers.front().data; - - const auto &indicesBufferView = model.bufferViews[indicesAccessor.bufferView]; - size_t index = 0; - for (size_t i = indicesBufferView.byteOffset; i < indicesBufferView.byteLength; - i += sizeof(unsigned int)) { - unsigned gltfIndex = 0; - std::memcpy(&gltfIndex, &gltfBufferData[i], sizeof(unsigned int)); - REQUIRE(gltfIndex == uniformElevation.indices[index]); - ++index; - } + { + // confirm that the elevation at LC09 is empty after decimation + float decimateError = 0.5f; + float thresholdIndices = 0.02f; + std::filesystem::path LC09Path = input / "Tiles" / "N32" / "W118" / "001_Elevation" / "LC" / "U0" + / "N32W118_D001_S001_T001_LC09_U0_R0.tif"; + auto elevation = CDBElevation::createFromFile(LC09Path); + REQUIRE(elevation != std::nullopt); + size_t targetIndices = static_cast( + thresholdIndices * static_cast(elevation->getUniformGridMesh().indices.size())); + auto simplied = elevation->createSimplifiedMesh(targetIndices, decimateError); + REQUIRE(simplied.indices.size() == 0); + REQUIRE(simplied.positionRTCs.size() == 0); + REQUIRE(simplied.normals.size() == 0); + REQUIRE(simplied.UVs.size() == 0); + + // convert the CDB + Converter converter(input, output); + converter.setElevationDecimateError(decimateError); + converter.setElevationThresholdIndices(thresholdIndices); + converter.setElevationLODOnly(true); + converter.convert(); - const auto &positionBufferView = model.bufferViews[positionAccessor.bufferView]; - index = 0; - for (size_t i = positionBufferView.byteOffset; i < positionBufferView.byteLength; i += sizeof(glm::vec3)) { - glm::vec3 gltfPosition; - std::memcpy(&gltfPosition, &gltfBufferData[i], sizeof(glm::vec3)); - REQUIRE(gltfPosition.x == Approx(uniformElevation.positionRTCs[index].x)); - REQUIRE(gltfPosition.y == Approx(uniformElevation.positionRTCs[index].y)); - REQUIRE(gltfPosition.z == Approx(uniformElevation.positionRTCs[index].z)); - ++index; - } + // check that LC09 is using uniform grid + std::ifstream fs(elevationOutputDir / "N32W118_D001_S001_T001_LC09_U0_R0.b3dm", std::ios::binary); + B3dmHeader b3dm; + fs.read(reinterpret_cast(&b3dm), sizeof(b3dm)); + + size_t glbBegin = sizeof(b3dm) + b3dm.featureTableJsonByteLength + b3dm.featureTableBinByteLength + + b3dm.batchTableJsonByteLength + b3dm.batchTableBinByteLength; + std::vector glb(b3dm.byteLength - glbBegin); + + fs.seekg(glbBegin); + fs.read(reinterpret_cast(glb.data()), glb.size()); + + tinygltf::TinyGLTF loader; + tinygltf::Model model; + std::string err; + std::string warn; + loader.LoadBinaryFromMemory(&model, + &err, + &warn, + glb.data(), + static_cast(glb.size()), + elevationOutputDir.string()); + + REQUIRE(model.meshes.size() == 1); + + // check mesh primitive + const auto &gltfMesh = model.meshes.front(); + REQUIRE(gltfMesh.primitives.size() == 1); + + const auto &gltfPrimitive = gltfMesh.primitives.front(); + REQUIRE(gltfPrimitive.mode == TINYGLTF_MODE_TRIANGLES); + REQUIRE(gltfPrimitive.indices == 0); + REQUIRE(gltfPrimitive.attributes.at("POSITION") == 1); + REQUIRE(gltfPrimitive.attributes.at("TEXCOORD_0") == 2); + + // check accessors + const auto &uniformElevation = elevation->getUniformGridMesh(); + const auto &indicesAccessor = model.accessors[gltfPrimitive.indices]; + REQUIRE(indicesAccessor.count == uniformElevation.indices.size()); + REQUIRE(indicesAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT); + + const auto &positionAccessor = model.accessors[gltfPrimitive.attributes.at("POSITION")]; + REQUIRE(positionAccessor.count == uniformElevation.positionRTCs.size()); + REQUIRE(positionAccessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT); + REQUIRE(positionAccessor.type == TINYGLTF_TYPE_VEC3); + + const auto &texCoordAccessor = model.accessors[gltfPrimitive.attributes.at("TEXCOORD_0")]; + REQUIRE(texCoordAccessor.count == uniformElevation.UVs.size()); + REQUIRE(texCoordAccessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT); + REQUIRE(texCoordAccessor.type == TINYGLTF_TYPE_VEC2); + + // check buffer view + const auto &gltfBufferData = model.buffers.front().data; + + const auto &indicesBufferView = model.bufferViews[indicesAccessor.bufferView]; + size_t index = 0; + for (size_t i = indicesBufferView.byteOffset; i < indicesBufferView.byteLength; + i += sizeof(unsigned int)) { + unsigned gltfIndex = 0; + std::memcpy(&gltfIndex, &gltfBufferData[i], sizeof(unsigned int)); + REQUIRE(gltfIndex == uniformElevation.indices[index]); + ++index; + } - const auto &texCoordBufferView = model.bufferViews[texCoordAccessor.bufferView]; - index = 0; - for (size_t i = texCoordBufferView.byteOffset; i < texCoordBufferView.byteLength; i += sizeof(glm::vec2)) { - glm::vec2 gltfTexCoord; - std::memcpy(&gltfTexCoord, &gltfBufferData[i], sizeof(glm::vec2)); - REQUIRE(gltfTexCoord.x == Approx(uniformElevation.UVs[index].x)); - REQUIRE(gltfTexCoord.y == Approx(uniformElevation.UVs[index].y)); - ++index; + const auto &positionBufferView = model.bufferViews[positionAccessor.bufferView]; + index = 0; + for (size_t i = positionBufferView.byteOffset; i < positionBufferView.byteLength; i += sizeof(glm::vec3)) { + glm::vec3 gltfPosition; + std::memcpy(&gltfPosition, &gltfBufferData[i], sizeof(glm::vec3)); + REQUIRE(gltfPosition.x == Approx(uniformElevation.positionRTCs[index].x)); + REQUIRE(gltfPosition.y == Approx(uniformElevation.positionRTCs[index].y)); + REQUIRE(gltfPosition.z == Approx(uniformElevation.positionRTCs[index].z)); + ++index; + } + + const auto &texCoordBufferView = model.bufferViews[texCoordAccessor.bufferView]; + index = 0; + for (size_t i = texCoordBufferView.byteOffset; i < texCoordBufferView.byteLength; i += sizeof(glm::vec2)) { + glm::vec2 gltfTexCoord; + std::memcpy(&gltfTexCoord, &gltfBufferData[i], sizeof(glm::vec2)); + REQUIRE(gltfTexCoord.x == Approx(uniformElevation.UVs[index].x)); + REQUIRE(gltfTexCoord.y == Approx(uniformElevation.UVs[index].y)); + ++index; + } } // remove the test output diff --git a/Tests/CDBGSModelsTest.cpp b/Tests/CDBGSModelsTest.cpp index 33f76df..a897be7 100644 --- a/Tests/CDBGSModelsTest.cpp +++ b/Tests/CDBGSModelsTest.cpp @@ -121,7 +121,7 @@ TEST_CASE("Test GSModel will close zip archive when destruct", "[CDBGSModels]") // read in the GSFeature data GDALDatasetUniquePtr attributesDataset = GDALDatasetUniquePtr( - (GDALDataset *) GDALOpenEx(input.c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr)); + (GDALDataset *) GDALOpenEx(input.string().c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr)); REQUIRE(attributesDataset != nullptr); auto GSFeatureTile = CDBTile::createFromFile(input.filename().string()); @@ -146,10 +146,10 @@ TEST_CASE("Test GSModel will close zip archive when destruct", "[CDBGSModels]") // set relative path for GSModel osg::ref_ptr options = new osgDB::Options(); options->setObjectCacheHint(osgDB::Options::CACHE_NONE); - options->getDatabasePathList().push_front(GSModelZip.parent_path()); + options->getDatabasePathList().push_front(GSModelZip.parent_path().string()); // read GSModel geometry - osgDB::ReaderWriter::ReadResult GSModelRead = rw->openArchive(GSModelZip, osgDB::Archive::READ); + osgDB::ReaderWriter::ReadResult GSModelRead = rw->openArchive(GSModelZip.string(), osgDB::Archive::READ); REQUIRE(GSModelRead.validArchive()); osg::ref_ptr archive = GSModelRead.takeArchive(); From afb7b416f519b3269d16f60b00003356e89dfd66 Mon Sep 17 00:00:00 2001 From: Bao Tran Thien Date: Sat, 28 Nov 2020 23:04:52 -0500 Subject: [PATCH 14/21] update CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 83399bd..6c13668 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ Change Log * Fixed a bug where GS model attributes and positions couldn't be processed due to too many files being open. [#23](https://github.com/CesiumGS/cdb-to-3dtiles/pull/23) * Fixed a bug where empty simplified terrain mesh is exported to gltf. [#25](https://github.com/CesiumGS/cdb-to-3dtiles/pull/25) +* Support window build. [#17](https://github.com/CesiumGS/cdb-to-3dtiles/issues/17) ### 0.0.0 - 2020-11-16 From 60f2c32be6f7bb2af02d7a80f0b212dba70f7219 Mon Sep 17 00:00:00 2001 From: Bao Tran Thien Date: Sun, 29 Nov 2020 01:17:42 -0500 Subject: [PATCH 15/21] add window build instruction --- Doc/Window-Build-config.PNG | Bin 0 -> 18714 bytes Doc/Window-Build-directory.PNG | Bin 0 -> 65351 bytes Doc/Window-Build.PNG | Bin 0 -> 23271 bytes README.md | 68 ++++++++++++++++++++++++--------- 4 files changed, 51 insertions(+), 17 deletions(-) create mode 100644 Doc/Window-Build-config.PNG create mode 100644 Doc/Window-Build-directory.PNG create mode 100644 Doc/Window-Build.PNG diff --git a/Doc/Window-Build-config.PNG b/Doc/Window-Build-config.PNG new file mode 100644 index 0000000000000000000000000000000000000000..249ee8c751623aaacba95921275bd12c7f6a5f8f GIT binary patch literal 18714 zcmeIZ2T)Vr+b$ZA2nH!?0F`D3rHcXr5{e3lKu{4CLJ_4&DAI)_6af_lY!nd)(mM!* zPACE*B}nL@_ugwllDk2FYWhPEAxNl{vj95k@^B%K{m+-rm_Ph^GI6_4Lr9~GpZdCUA72BS{vc2xO= zq=;m>hs(kCT3Okp`ugUz$deRpI9FF2O=xW|AZ^d~ZYfJ^9f5vvsBW0qo|GjXJ+Ux3 zNT*JYeEaL>A!yllv%S8q()Qhlv@XiKu;0Rcp1vepv{Wsdt}d`$=HWF&qX=>)zstym z#e`AUF$v=gyU%-D$g@I}HjDkNr2{0s?o@_H%hZJH*{yl z0Dn_!bB7QlLHp2KPVsV#2|>*rnZ7$5YqB%KGff{8v@YIjjg4&a_5BtbZv0#mfm$yp zo?e&bzDrfySz6?M0$aGqGmKjoD=650;F2q8x84`mS%_<&@Nr`q1)VV7DRw$O86Cu7 zo0geb-+`=_!)g&r>>lv-9&1hEFUd(+)s#@FYLvi#q1n^o7v;8=0x?~;($)N0`B}*7 z1baN$p%puXtGDE_$>TqHAFr*u%I;$7v{9NDe`E6oXJ1_e>s`931g`#@6eZc2^d8y! z5O2OgA0Ld#I$Qh7F1{=Q|Max=2W9*M?X9&c>Y6Q4+hypHS?^6`OSG2jJI7_q4vRR% zVPvOcnu^M^U~y@G8O_1K{k@&P5QX{qDM~VFu68cSBGDu_w#l1h4^vFyqT!0?-NCLZ ztqp+zStrp?Mk0xnN9bsNOTEbt#=gFb=uJ$DF8UdUY)DhbIK)BEbjQY0=PYMNf+nM_ zx3Jhb)uQIwh4(3abr)$>;?eN;)-wX2a?QaV-`NkmuteF&_pEpN&rPtY(Up8r8Jg5y zZs#7Hb6mF4w+!UxMZpGtjSs8pQBKL2fd`Vr>;##d`n!45AN*ixHVJH+*zodrTYc|@ zg)YhXQps$X_l+;{ZJt)hAf=WhXEq^83fzu97ovKbG#30ysn?-lpoVhKMf9>F zv)GJRn;{$<*g?fsTgIa9MZ0wN6pxD=z~PrlWc%*f??1#$@wTsaYzx{{&b8VY920M8 zBqg-9&5uSZ<8=o3NX9z{O|41oFqsn1nh=2SL!OYya<Skg;gFy#C)+1hy?W+sUz9m;@D%&TS+96Wibfeo z7ne-J%Sqrn!3`IU556F;-xoIu!)YIYK$R+D8*z*>H6|XTdM#g z+@~%q4n!MOD98wtn@^Neg-PsJb(c-u3I}&!QAz~|uNU=L z3}d0aUl(YiQA(KGw@6OI^LqG~#|TQ?fyR3l}CVS(o;ckN1TNoB}bX7FuZxyICA6R*}H2x9h{ z5`7S=2uzW*e|YNxpSrbz==}1hi^=L<2~@m-HjZ!j zGNQOEq+6GjXU1H*w&F1R!lR^5y*SCiZ)>%!o>mvGz_#aQ@G-(T^^1y8xUcq)k@`LO zMiSh{$uZ^|wc{B-_xT>(0d&(qW8&t^5l^4N0GHlGciMRjKDAeZR&sRu`B=CRlH_}r zPpir)w5x*VNGr5u!|8|-0s%vFD!|ayX1dKo2x5fe2snJI zQmfsg;3cw2KHcHsFaDgl7l#!=+%`0e3HERu<$}%R)|d^p%lNs+{M-ZLY#RjO4M(gA zK{ugc(ZX=8;yho7wctClUtwP_>U(68!>yjdaGZ9(EOOe2TOQ99NSWuhsaNHlHY?sh zNan?iPr~towQKL@$_~lJHc@VPMTI13i%rX=*p{ws_T|p<8Gg?1OAMW3b%R04Iu{h& zib>YNdiJFjH7k^Yt>eY!x(@^H^;)P#87w59J!xm~{MHyb6zN329}+8r2@aY{4uZOK zxQrJjZ+YeruC2XIpq7nuku6+!xjR!AXfvk#+&8SILi-#SZS3c}+EQxjx}+GxE=KoQ`vr^CrYBWD z3*>+}$9wMVJGbTOSl<$E*k&C7?X7D&f=p|D=Wm_3&-4IPqT>CBo3!s8k1^cg@!{|K zM#jrxB9q;hWs6#A%arRmAq3G;rLf>!=cvB*OQgdkEa7CY@do*Y2o4EYK|WI6aFLI$ z$lXo*>6%WvdiskS_s`Ve7Lx)?QM z>t|gbQNi0Kd&^LNV{t}iZBuui+EFDh8iucESnMetc)B@K;D}`tAtfec64ApZLiO|E z3FYceZ!`Ua&l1WwxJb_`_!f^k_KN-iO#(Ij%UPq3uu8BOrv@G)zm26_UD_|mSHbd$ zbYD@pv5rfA^dd67(Ii`0;ZjemMNFTjwuCI*;@QpUXo0?ClloGR2P#bggX^k>0hO&KOm_%dbU~`n7a)nbe>!la)2#>kIeE zuJHrHf%DYNt@wI`V{PfT@n9+8ALUmRAkn6WE)?drR2kcbm8A!cBA?8~@ zSN<&f>oK}KBO&vB7ke}`P#^8vxq~}as}wBfdf5YSkpHR_V`;PbE@I>nXXh+*z$W7lQqb4BI2`4OqI;kNTUt z`!tmp-~(;iT#vOma`(upHfE3lwx>yp*{w_8T<*(Rns5&q^lcs}M(xbMeH#7^w^83g zk|-WrrxUx{vtvb1_aqIi1We@PVFtL6>Zr=c#z2RY?@p`QCD)SjSl%4Qa&QNN>`8_>;;%v6b;G2-CQZ;~*cFe2kS1SiO{9T`Y ze4Cygj;*9`H|V0%23EofCzJ5gNl_iy==F4G$oAwrb){@}zAamF)Xu1QUct*FPqrDL zCEkXoK9M5q{PNx&leVV~ZIDjmY9wAD3fCQYFOgy!oTKt8KKC8|#9kEgoZOLGGeTdi7$RF3w=(}#$n$SCeXs}b?6_pCk+rQlw@Ugl~K%eXm5 zv5m{t3XjMykp|YqwcDe;Ue}N|T)}=tIFTg+vzD8eKGMK(uz}LYD{f~w_RTh`pL=RB z5!vkk6Z6gCkHdY!rAJxcLBkLUDK45VQc3eoYXcOu2lVtQ-Z)$uDS)lh zmMT6Ktjc5$^0Z~Ub#nCimiWcvDdh8|tIBkZMx^M7X||$w+rCp6(-xhq#_Yu1dmk#iV5yE@ zDe{dJu^F71*_F4O23A||7wg-1sMmUv8_*X=%aOl!=3hMxk93AjhFOy^=ei_j_?)EU zOX{-Z!`hGpeh#+acMrP`iUIl06n=-|#xkYA7%V??-|_h@kWXnO+ijc{%ggOP@}wbH zdai2Z$Knq7aA))3K{>I!w+#)oS5Jr(n#dgxdw(@YRp?V4MH(rADc)EQv!GAP@dRAi zHaCS*x&bGboBoRQ{!p)41a4buvFwT#QS?dFM3=z_^2!cdqt2bOmur6oY}>ODsZ$s( z)EVmD90q|Aes5$3>cSiC2O@7On8TKYDL^Og#>|R4iN)wizLaSP^J4V zap6`Sx#7p_JF8LF2jR5UAd{U_l#=V9sSpYu|4D`$2}^UQP)9u)YLgatUNC_qxj!6) z`yL{QKT7y_lek?Xu1GOS6d z6KZA_ec!IjfDM*1iIsV8*@WK@p)Ej^Hqj|MBKAY6N%%zO)j;s`)+SN#!LhvNnv!9qU*J+QY3TurGCl1S^bCpH@cGbXN;oG z66-pJwL8nhu|rewA<@wv-O`A)|TP{Q%-+?IMHnL1_vRj2An z@+XL_sfG(#4D`_&gL;T0Ig&c641=?rhp6i5tOUioMga*8CE1nn%-cc3QgnUE;7j&p z74lno823o>*!oJ5)X#-8-HsW()L}e4FuFu&8wF+z&6<)N3QqpG9H6C?60AgK3!vcz zEa;dC3j52|aO*)`w-iYz0nRGrPrHhXmwRKb@}$aEEc0dm2n#De_}+y$`@HU$ zWu(hy;bfl^K{l?+OCdpKIb7k(IZC(jl2)x`b;4KtD|v8u^t}sx-X}Yz3#E$KK}qS< zb?eBnk~yNRU?Q!n%kI_JeSRa)9s}SLv8F}#S6kVRseN{PFb`MfcX=bf-cGbsSTu5> zZSx#0b&Iv6k&}a>#Br&O>}X2jD!#}bwRz{9Xe%8*(pST#VE5)5t53f{+TimYSbH;O z1<%aJYRe$bH4>&Uz&%c;YeQDgJ@WHCH``tj`%|UreDarb+#TX5c7^5dAM!lL#wB{$ zU3Qj*K0Ce0XA2wX9BavXY}n@*G=Te$Qv$)0sOzce?)*uM=eDZi4Wc-Q#`4w#3kNta zmF!LDXi|#6jb{wm`?wI;BAyj-R22-5DB8vq%ny+r_ffOi51?rrvA3g4DjIg|{22K; zcebK7(*>_C>q#5&oFAlZ6m|fb812+3R`|Hl$W(D9nR5aP<@4BC zm~(TB)+w1$a=h{$N4+a}dSN*p(}nSH3!FB$nQZBw$;fMw!JypsF;VH;qbzMJJm;^_ zwKu<@tP?By?H`+h)Zc&mGK}O;u}?EgNQ%?rRG9z*45GVaU^5veHAs7>EjsU#du2&hBR-*MkaFN)#B)oM1^7xN? zsr3ve_p#n+FJCHlJ3venhou;|5>caSEAF>$N27Ub_Ji*UhMW>L^fBOqMl8pz zmv80SNZ-#8^55ws^XJj=YW&@zm}v5i!_WleSj=pO%&ZQ)wR~6AHJQQvH^qI`t`}5T zbqdZT78j0|eCF<;zKfM|ot}($sf?S>R$Q%@MFo@uv1j4?l>-?+qx+PZ8Oe0|O1R){ znCFU*?UxCH=YR(fR6uPh`N0e1q*0O`eS&^xac6As)#y5Mr=+{!WYv7QV4RM^a%i=4 zmW+>&$zv@QpAc*%S4Di~S$-I~=-a4^T(RvWJM{{aD|M|nt_a;+h|>5AnKQy$k>sE2 z3rkV$1~mO-@Swh+TXLo{{6zX4haT(1YO$LS*TVPji&4P8Gd4N`t<*~%tXuM9%$!v} zRL2bv32Ahq+Yp~AC*BiQxnu3~rAc-jYkG&js9P|6|9Kx=rs`jC><3f5W>tz(I(Zs! z5w7<2>tch9!|$0T@n%)ccV3-8Glo7=+FmDU5?6s)%Dh5fU`bNCzZ&-#~<@0HjcToNXbLfWDzE}M>&I6Nv z1WdZu)uCK~Y$AK6HCEGf@qzL47=ryTujOrb8Jjj>tj`H9@sOXt%HR$CMX``8YBt=C%h zKTAsJs?Ull71Q6z!7j)E-tsyB8eU$w)x^gNR@Ay5Os319$x-p4nQBo|eAvPHo(sk5 z^WBbbL<1gEQA z$d3vQ<5%NZcReQmVrlVV4!v+NgYyXYL4N4>C_+4}sr9Oii&bdsxfpi!M5h3Kdfz(- z0s~nmeY;S$+qw^#{@%p(O&0F2sEzL+Bw^UvJ(TJm=Y14iCElQ#; zC#QNvlXraSr!f>vn|~whc^NEZPczBaN#n7eX0F2}i4~5?3Oi~uyyR1IVoS}ZnI!n` zhFgv{o^5QeQuR|fBze3Gv1z5+4DE7XZkh;D`aJ_o#CNFkt+R*OA!)rjeG|@CR~kj^49$A< z!RRM&)9~qelWfG!di3V$ROFZOmy~2%_v1!mVLUdo$BFlBb#c0F>IeuWSvAEq<&N~8 z(|A4{RCxA!MMczh2FFfZao9iwCAwjY{_M-qgIhO_L7g`|Jckzone^wp97MI=CFnA6V-B?7>mF-E&k}o2#K{EJT3i+*Mgj&DbdPv!CZ1c&(2h_?EIp z2OOiwnd>GIUpqNiA8CsTx6(t{SOeG~GreZP60#rL>r(_e*UI8tJQiBt6Uw{I1YQ9V z0DaK)>g>#;(!ZR3qK~)X8=eJ84OTD9&#G`vsSq@E2f@lAoOcuxV>AqlN}4FSe3FYA z2LxA#t^T-|Z53sjTXMIo8+Q{e_Ao5_1ZwQW0Ra}sJ%q3*i$Bvtz`dwJ_MbrtTu)(# zw2f$8NZ2{m%58#vXNQU9LTvkH-v9pUdY%M|e>020t)lwkg(L!cv+xdC@kOi5jG1)X z#y06gSs2lE!es>+Imd$;cWKUtsU#rSA@>SI^ifa7_+Q*>w+SeQ9P^D6akr08tsG7 zt6}e_A5~Ng2A`Wsp!>f*gjrw%>%Xm9BaD}1%UgBFB-Rqu*+r>5I;CQ04J@p=Y(d7Q zqv~=wMxta&%zs$^#1de$Y><4CJ-%Gh9)aX=pY5OIw@~sN%K6Z8C%Ix%#bqm!Cy1#3 zU>!U3W7N$OPr@qah1gDQtIoWTKx`0gYD1NY^(IkH0poOVMRRctS~-cv2d4IjxFXg| zW|32mQ04{wS$~+^;V^UyMhiT;A7U@@_y;rCaaCibQPbCN$@1(aO!S4@zp}T@R32zx zJI)u)<=-B*SUe$FTYi4c1Rh(e2wU*yG5*1`V{H}6!MbDWjWvY;Y$AH5bsPi)&0 zP`Aq0l{PsnmM13xN`gCEGS+^QLq@=K`!4Qu>7WS^SoTsFPS2}fl>uSS%UWyMgWGD< zhCIX!j7dQ(%bQ($2K!sF`}1O?RD7b7y7vck*R7DpNGEg3DthHlK_$!Pr5Z)9h(GFg zsChfl7f1h87e2j0UfXg*bsMS0dUY%|XUz_nesQl2ymcSDc(KT9^5i4Aq21^!(@QV2 z*vv;fVRtzcbU%FUEwm)~Px#)ai@Xgnqn}ENJ>*)bIWll{IFRWxZEdxL)c%B-zEOzJ z9H0zrwscB(>UaVZ6AEM^lkUDZPw&4{Gc(fn&S2+U#Pt_o?tW-Sd~; z?w69muL$JvQ8CovGYe1K%~+Rk2~X-B{Ctm&9)tuOIFMo2!hgWiu6=eUi*l;kH;L5o zSdheuPCd#^U$G0ga^3Zzb0B)o`8J%B+|VpdaTJ987}N`mZP{Ttd1ER$8>{TO%0s`- zx9}$hY<>?5!X^Mr#+BeESVSizofM9EUsyhW*s&;kFC5yJwTbbFm7jU@q1MCv6PNoq zxcd!-X93PIAz2$j_tYEnuIR!No|O&8Hj3V{-R{+i+GWCa|`P2Z|gsh9f(oOusi@ zyy+<2yeU|G|2M|ud+lSy{K*<;mB9@|4%O=}(K%R6z5dv-Q!r1A!6^5L6+zReY%Bsm&6IR9tk9*yeE;jav63ZQsl@&~QmxjnH1sD7zI>;PO(z0m!q}dtk87GFpY(1l!!z+%(D+(^GQA$5%mV15nDR z&;*xW9j9J(yoF3G8(Y)Xd&WWTN_`|x@t3*v==BS?W`$?fw;dY~LGL1UQ(y;E8)xr$ zW}@29PgOJpZ=du_&?e>SdrqpmFTOAI+^o`Tb&hMv1YB?<7IuH^!bS>3PUCL#eQFJ;dH zk);hYIGywG{RVQdl;~esCUfk#F6+{m1l5PYur$WH^U~w{DOj`w6lzuM5^Pv_?q4OI zXR&&wuQ7}1oQY=A6Tkf0?A3;X8KJvnpM+5CeEj~3Z;ZgvgIj1bo(rHmk9@*fMn&;D zJ{Cy`Odr(Ozgj}p2;}LKHD`R~m3w@EJ2msYSroI90!bh_RJU-n z(q|j7f7L_L2Y#krT=&b3UL|Wp(0lY5u|hd~pSY%q$&{`hvfs4#_56Xw(&7AaCGD){ z;M+jutKN(FxjcA)s(?vrq5Sx~?d3a5A1bw>1aHY#i|5_^;IepDcxu6r!>ER*gQ~R< zmXe2f?zt8X)h8f=Kx};$d9;&4S0=TBzIh#z5dk$XqEA|dzcX=-W)yqi zR(>#5S=%H#N9h3FMK#;-s5mkn86VkaQZSp}|E4FVS-T)IipH;LHY=g<)PUmonB?#4 zTUl~^T4u5)qGwit*s^&YUHTmky>tG|)=;n3gp^;D zAT_RJ8c+Yw>p=iUA(L;YH0bu^;_{g>n+mr9-Qav)|FC6pjLsKn>HTIs}wvoXqf7xpX}UH@1(Z zoWQMtAA#@O@LTi!w&lTe*!Q1G?zpGugnUsdG{&*zF4V`2~?5U#hi4I+@~ z;RhGNs(5A(HSUfY4e_PR)Hwg=<^9=y?Sq|I*h@#+G1hrpvNdz~T%SkQ_G>l6rEH+I z0{gyHn{8wuOq<>M?$V%S;}lByjoPiV!uQ{XR^G(bu(6)wf}YU$N3qzB?$$ok06l`) zfZmwg-qB)!>qT3KYmsJ&h3K^dPW^n417sGKjB~RC*6`N#LIG$GE7|Pz%y4s|?z!eW zx+HeHp2tm>C$P#GH6QU6q-pD25{Hx#clt4?)oPAufhwa&k3c!Hk?*k8pE|_B(5u&6s*wiCH81a$5-j{-VO!2V*SK-+P_5_SM_eWqZL)8hJAqITshzi_~2ya9b%#`YgKIU5KfK z(sS3=+Ma!$$&1aR6+hx5b^Y;|HzMw?$?E;C1Mo^F$Ww7K`d)O<&*}Ojsv!q8LM*@Q zC~#AaV>i+pN+G2@2=c$=ab^*828$*yy9H;n39i=H@aul4JOm%p^L-!9Dl zPvv@~Xcn*JgOSPeObhHO((@W?7*ua8fU(wI5dq7Ja#UbCIn#)>h{ViSu2^oOF%4W+!%n)RnZrt62G zAkYn!3y(l+TJqcsp!5(d2qX!?Ie3hVVD>2!SRf$NUf>NBuM7tr3=W zU%xrGj6;&x)#T3sBL@B5-`uk6_d}$&NYW+7eV_;kurF9C%n1e6G6aK(`q7W*89qioL9!)z0HT~2xIxmYJ)t}MNeqL~ZbuAi9mSS4C0P^lo zhl4;z{TKXvzgJx5=YCNi$oac<7l!%?*i~`ko+^c`S#krDtDM*ow6<5ou-sb8e zNngf{i=JiF-t1}Y5G-|YM;ibl0-#v{jWr%mc3M?`>eaxaE$baChd0y&RFt zScKn%<&nmi4lF1H6k zG#JdoQuA|H+7TB4Xo4I2N3MzOQiz3rxis=Hv}s~<;Q}Y#CO4<-60~)-)aRN!!Y%?q z05*+(Ba)ei9r}4%SI=^>SY!T`Yxwhz08X5#2zV&>_?7Wk!%(hyc3HU}0usWa_&G-= zw+B87JUio&4!(aOfMXZD+Umxlm>iS73o&Q1BRuEudn|Uw>4f1l`OmZqFRy#@UZN0vOO!k|( z>DXsJqcsu@27&GZ3lyNXH>MdJzAnrVavTKu{P)E@>A=2>f0koUBY|=MJh5yEeW4UM zblW9)`yA7*VA6lHi~K>zB~+I;wbQS(oq2O0ZDGui0|es#=UHHeJ*CVscL>LHQ?mui zbm#l??nDdgaDbFA|2^DQU_6jBz-+%K!MkVDED-PE-@x-hzkF>OaEdQUUYhb*V+aI+ zzW*(F=V;)uT{N~N)j-Og{}yYl>$Fl~CebYa9*`d>quTj?E8U+~w-3a5 z1D&v#i1t`~Uy8=wScHEa1;9yv8 zF2)V*)#YE*Z(-us1*DVb0C4^oQ$9PyVuHcjv`fteQ?FSS$*->s+JH-e7KSGlVo zP{%`nmTixofHqhhM`@uizU75(`9IqzT}|;_d+8G?e)Lw;c-W#C@snW%EZXEppf97f zd+j$CNYX37N|G{ny^{Ar0SBb5o)tovDh)YsZ9f>Tf;6tdA30f`t}I-E822w)I=0`x zl<`G9-%mycx007n4*ikqw>FfpKy1yvh)gw08s4qn4>mqzyb{TzDHk{Sj&qWBzyv1l8D6Iu94mOm{ThG9ytvr|5NON=;1e_tD?Q_W*7B4J z+0#mXP*L(n;K8bG2xdN(qj9kK#KBF!eV%T9ypU_+={BV~pZ6Ropz3Rd0*YCkPd(xH zxj`Tg1Q@J&@(ayaIE=yyIc!~JTwJ!6O{Fb4qk`O6&s3}U@K4-2QY`B0Ykg|hghzkv z13Uz9YD=!LPsrre6n7Ph8C*-y44n{BdKP+tiI?FvT+-aQvBGui+oM9(Js$d_hW%jh zsu?dYt&nR?5IXkSg;J_uV$C2(6MgYT8K_!E+dB3A^GIe5cIcE#P5EZ3=hITnJxQbY zgEtO&S2ORpa6zZWw;E%lX5VuNt*l;$E>2%uQK_C=?{k5G!O|1!b0ybBMe@tZ$( zBw#19X?}#O04vTP`VAg0G@)>l2eKmW$A}1}yq+Cyv7||~O!jYwa&KiB<(eFZK91Ek z?J9WmAa1_a2C!3ryCj9>u1A+oimy*Og`_EL&fOKVsBufv+EBs@crF|&kd*~=Y#DI< zpg--mf_>pfv1%_2J8EV*(nM;PL7gk}}bat=s-;krv8fF>^+zVjUQq;a@%Oda(b@?|%>6x$AnAH^WW} z7g3@NZDwmF(w@F>tn4nX=icL5O%p{s?zUyTzRlxex|C2tipBxU@b3ryIFWiL z#*kFXrg74M`N!QB0K=?0{Sr_k#Kk&?RwJC!@VcKrH z)CcXc{uM1U#_uYBUN|(V>)C1(+U>6jEvM=m+b@7yKsf#$^mqK}d>q?W znTW+Y$5dXr4G+z&QjYFokRK8-+~XwR4TzTIg>%5}E8(=tNs#*r82w%G(Yx3d9tb4A zPrg2rw>9jKp8O7fSFxt&+zfo^a=y!Y>)QXNB;_0~9aYE5FoS1rjrlLngkZ4_QJ9CJ zrF|o1@JjWj59K#*4pxPl=o~*+lmNocxOj*Cne|_h0wYN8zg%Gg|7}RWoBV%t;WiwE z75oRfzeBVC$C&Sb@6G!GSku8?;2e&-#{A*Bb>zG3%=un?91hpnu4HB;*tB71T~mH; z;-7rARZh6OFpT?4%>L_zhQK)$TkOmJ_0Zmk^13=&f30J7*@MjKE|XG^4SK%~{YGEO zHD>ViQ!SEcw(ggQDk>%HPbn`P4+XHFh5sw|c2%hg=*2hk$8b+w4*F%38?&GDw$vAB zNlSi~(IGLwmxm5X6dJ>a?6br6Lw#JbE*il@ajy>CPFOw)FbU|^pFo5SVsRXrxBckq z`LaZO+w8QA%viPj`JzNsC9eC~8!9T*qRDgX`vTY@y?s>gjekuE_!VOEiS;0tVt3s| zmhQovp~Wl0KVak&Z^MC5hxMNkiQOn{?Zjlr8 zh1sXmJ3Y=cc`_ISY+9Sti;zWdC?IPhebN>w3*&^o=!EgGMBt)Aq|4?-aC(p3eIEWx z4r{n+kbWVHc3?AW*Q2;tuF?U;M=E`${S1dlF-`GZ zC-JrliiH(%^X;pwv$2Dnt^DBWXwHQl>4s;Gg*>}8ioY_=-Ha>s7(KhS&4g}Q78(a{ z=b2c2ZDdbV^-;PbA-u%;$gDixk5NIp4;SR4e8GbEYuBKC&E4Gj%t~@R*T2GNR!ASC zzWWUr{0pTf@zLe;-oz0+JRv#Oz;q&{vMs z;8w54HTpO^tAJe(_S^7)@X0me&W|L2BrVJDibC}LIr&Bj?hHG*6FjBQ;!H&Q;#rZ> z7!P=8l+ZI$_!VJ|4F(mJ@c#mtq&eiSq0#==PO(OFEkXCC2TILPh|jRcmyt_%GtOPlHx65qZ!oE8 zc8`k?6Td^7^_VJPtx;1^c~}-y>-rr>D3qdyQ;c{Y0=ZS(I**B9=D!vR;5N8d5;#vJ zsas5}+VcIQPKoc)jx?`@=Ly5AyZy|UK$&_g)4Dh2HfYr={->@+ClRrg$0oDl^Ka$v zWv+Yt;9sZ0;NAO=u>V_2Q2+nw)|>;fk%>&r6hmsyAF12d_~IYULok6$H|!H?OP;FD z51!Q>{50m$?`>MxiVOmj2Bj}4-|&YWxwpg#WnRRc_;#vtACl3L*7H_IOXN+ zZAvG3_khLo`H7L7M0Ut~nMNLx7AN#B7_?h4lOo1J|53Zo%wTJcP?7e)kI*h&iW}=_ z*!_nN656>)UthKYJr=}DQ$H_n-ZTs3;XgsEDiMoyC(&D3AoB0_gDIQI^w(}aXX0Ax zXn`+EPr`TWY=0>5GZ@^4q<2gRDq3lVh&Bbx8ZJc}_7@nP80r&`)$BSOHK_BnTm6hE zf|E4TB`%wf!i@6fQvk07TL_FSk|eTxcu9KeF`CRX{%ct_e=Vvp*QN z4D3;rGH_^NZQkCgRX%Y^(NG=J+w**)OI_HQxV}ukU~+%axc8Q6l2(||;Moy7%r~9l zCpT=J*2o=J_!;VzSXZTtx{A32|IDTd-pY0%{DqD9n=OmM(KdPQ|Cl&LRSV?VUs~N@$COnSirfk=E9|B zAw1@n=?`Oh1&#pZ7;a30{@C6g@6v&^TB!Iek;Q^$U6JR6nRc^U|Bbf?Ag@V$1!%p- zv^-eQ{Fzt(wM!twNUV)CVj>sj@gz%M|GXN$Y#ULeh;hHo62=UccIEH){gtJgmGebc zLE;|q5i!cdxLz5H4IEGUM89Bs-$DX;PD9Pd2yR+f>lqCkdn#^roaO03XKp0Rct-zp zNrwpU437S53i@zUxmxF7lgAL^0snhOdZ}j{(?yFeCx?E)+ue>JuL>2e6Os|6@GQ3{ zaLLi~U}KzIrkHDibMeZ>QafoMz#GgDagPQFLYuDb>Q*hzFmc++Db{{d< zDqz6Q!k^-Q&Wwu6u!pOk!hsu4$0#5EAs`V!C`jvHg!FfX44~Kl-kt%@;ZI(?TjZ(~ z*0_G~cZvBD03GO_uegyjLpCxRB^qwF9mFW{(5)Q$S z-g-9^s>Uvhp|;n)rD>&9Adf4f$Xmba-zmkcIVOfkpuT9<3xZ5t_6Dij$?`=erToG$ zbb#5tC)R%V>L?2!zi+qC#O@qr`uLr}bD6QJ`&+*#1+Oh%(*SC+DgfU1uardmDpa4X z-mmOQc0Y9nSG%5TCs=e~39eYc$Z-d*ec@!M;;+@RT+J$v@d=ldxWtfirNi2fu! z1OhpvtaM!m0yzMKK=!p9qyfKCM7=!>{-Sc!QM?ApYlBUL9}ZZ`tIIE*1hO-rd|h7G)pT~dFPcX&al{LUxNP7U6>Z#-&}j+ZUU6JZ zDvy!q9JKuMo?T@h-Ql}Ou5m})P&)T8KrHR`)yEaIaY;$hX_5Cg6Q?-dYx{>Y+q`DJ zm`@T&&(|*~NKt+f`9VN2c}*?JpLlU{X5QW8!1uD;0YeqB*q{Lph}0pFK+d z^x9i3%3lt`st6tSqhC#5pP5hC584cY96v-yyLTci$qd~)F>~SpHx7>4A4>4O6W{Nz zkb5VPcV`H{oZQ6mz}ga}EJ1huSS9A5D|YuI!|gBbzh9;w9w8+TJ{Z*4Dn2~B!&K{S zMxcg4jhnBY$UBx6j)XwVcUO{kA~*YSH4kAM_h ze0vjoz~iq!upI5P5#4l8K^>DfL)FXv$A5-EUc7k9hJ+)3Y5TQ$Dv0l^f*B-)57!c2+8hZ1d%dufz7!vj2MB zPEEM_3c~YT|Q)|D+YBZ2@|{a z9#?$4Q|0r-VOC)`wB0YZGaF}pWD6EDlicKo4Uip>xlYxXu@MP4T~M|sNqfzhfol`u z`)E2DS!S|biW?XU-1b!U*+CWHnefipD$LON^2#KKuq({a)9(eHlXIH+^X}A zu?^o?c-y_q1A`{-k3g>HZDu#YRQt?jSEwMJmo)0@KKe+gW0IWC`X@!SdsZq9;!#wf zN%HknXK#7uDk+=89?+3t4))ifteN0tlLDa(+(Go+>)+~*rk7grzYA-v&&T)jUKpx_nl z?-PyKan#t7E#H+88(Fq`-QKDqE#`yqB*+pap35z(dyv)#Z7Om{KY!0Q5Qq!A+`Ss}sEpUU|pJ65+c8okloy*F2AOl4;8o5eeG!gu^xG z?$if)7pr?5IgBf?`>}a_g;jF?KsB+G6kmNyPAH+JaB85Jnc)JYcJ#OuL73rMf?s?2 zrsH{f9U5=%*6s4!{OA2XC71nl!k1#lOPKm!?w6>Po2z;1&b$200tQd`4DPupzl&rj zCs*0lkGsC@X+@53Jl@N{ofp+dekgeAFa|>&*ce*#;vmoa^1{x4k1xyr!W_W=!D}p( z&2_XsqhmHwTo4{AJ z-&v?LW{E^`vq0y>e>9FNxZ92gC|JFkI;QauVrXr_536s_k7$>$e{}^rg)>f$H=%N$ zDU>x3UyL0#3V*{7OOyEasZ9YxdDfx?tz_}m<@%H2;QKz@5V;RV@&ymC>&d9xHJ4Ui ze8W^ZAK#2GAB-q&Qhk*{c7?@mi9mO!YX&DoBJj3v^yIV__=6(C*h8+qbe* zv1kP@w6y%qj`H}_;YIBV%ex&07XoIKt*XVa#f-C0H&~#H8(bI+4i(U@mKWS4nNJsV z+j+*>(cuc$=4cbEWxbBwkNfSFtMv;9sUTV>gb3VQycs_o1@G9^PT@S0d%<^ELQYNkQ#y#*+ZPeYOu4bt_Nb5T<>Lqf?cPw0 z?M+5Lc~mA+!WueiS(DF=3TgAKf2(teN#^sDhDR#_3LHnL9hJ%^XHoAEb_ka8nSkQ6 z?^Cvnz9*|MTHf@M%@`hLV5SF8&nce;dW=j1^Zelv@?^6Z`Br4DUO9*DwCO83V+bT9 z&8PnD_U*f>#JnJtx~T+$kH$tw5mhqcou}<)oxX_-heK!+@#Ft&=`4n zdf%qL5G#qOv`z;!)veD{oRkIW*V$iqr#Jy~F(F_-^t|j^_!jZM|pZ;*Q|IAwQ`-Sq~T-dtvnBp{Hmd)>Yym zEz~)rZPL^A-Xl+U!%(c-s2ltBv*A0M{LJ^UNZ}@$XymtDnab|1Hw~Y}cbdF%NBMKn zdJqUw%i8Y4(Tn5cu4R8Fav4Q(V+Vx&$=OMRhKocF6YY+?mxHEkgNVnW z&DaR-_C+Ijp;(GlF<19F?b1jI=Xk46V`%?Ad4<~tNXGP^+G3@Is>?Y=35QL6yZ{=^ zALdI!W)5kfIta6clZX#7Ub%N0cwj`d9zb{y7-J!~1SVW4H>0ltRSoA3-p}1Yt>H}; z>usSH-A{I?3*pkj+u)V`3h>R2HvE%io)_Fu5H3CzCvt8^L~t;46Q1`W#wIe}UEZO@ zO^B-7Hy!yYLFJnpoDK(&rp^iRD;$`xOi3fLb_T z0y0x}ddTa;`LKAdFzGe&E?ROouCn>tr%MeoAeSLo`ACE}4|FINsdXtj4DIpTZJHsk&1HJgU5 z(tBkL!QiD^xkLzP7*nYxTY-_c{aSL3xQnEtC6B#oU4XxcK^h`tQ$sK0W?x)$eW02v zSd0)wg}L5$8?4?3xvKH#o0d*H4RvCjd5qZ?9$1GwxwgjPiK_BXF=vm9GW*LOUU2p} z=NsSpE~it%-Zq}|$)if|2Z%Vg={PBwq583^O!~G0;Y>DvPZz-}F8xj%E}dutMDz%E zthW(rbjDBvUglWTC+L{x^rG?+hSLt`O$A@!ivt6Nvvu%LSbti>G=JCaM5HigOp6~j zy8!>e6(%+TA^ z*mI1;cih8?T!j_}uAyb_iyOqesS)WSoP)=Qtb!u4^IY*~!*j<==R~cUYEQ>Mtgjm! zL=p}3zAFa}#{~F;C#eiC8@3mw3rda)XqXUfFMByfh@g{SGP6Q8sS1C{xqt2)73AtG z-EW79*#^C#bm61!Vn3~E-;f-$e4Vrq%RC1^+Y0HpUpOD^_M%VBlFDG+^*vpi6I~>$ zNt%~>>q~?|wnb77{xl(cqRl_^9y8+{HhB5%kAbLm+}Yr7&&XfHTF2TVZzJxHiOnf1 z3qcsH;^=}l=V>C5>-R~!VL659t#@P&S~N34M>_#qK?cQ=^Fw{h;yTDf+ZfS#X7 zz}ThjGVFKH{u0%|%J7>i@Gsb0t06KNR<2l8xROOrI%nz14wYGf`elOF@Ze9hO7*R% zTVdIvd<3msWEyxkJBN5+=Xb&s<96G5j~e@3+bK|nK(1Nvz#Qb?dfD`*%5i*STo9<{ znna^>ZNU4fx(16bwZupGiD1U}tKc)FlTIuOzK(lc^vv&`c+X?uEQ@fiseSC{2*TFh z>C0Dq+GaO6pj4o$zSgM*?{X2ZT7I^%(AKnTiC}@Q<^}Q=SL8*~Q6myFXCmz(5T~!y z^(-WItF@#*m4(5Y7eUt}%z_2{rDYW}e z^4784Q}^o;tvI=$WgXdmQognyu6wy7<73nAr&XSccr zIY|>YwxWk4`A4mzS1d{mP8a*6TNL1@_Fpi_xKf};{w`*lT;W$7k#_y~(GdBMGeU8q zWY3cA8)7c?Z%>`f`Yw13{Qvv=v?*n!gm8KEz^5stVZF%oUfbqK$!7%DoK63il&o23U*)4ud>?zIY zD>sSF^h8DZrrx5s zy(BabhKL#K3i;@vc}oNYGV(L_LA`wCdZ3Qo?f@$U@@8v( z9fN)WO0?gKSbkwxMy{UzjYjzgDCp1eF@L#`Z3JD=paHH6A7+v}v_Ycn&cdQ~7Ir2Z zh%J%0Y{zy_wM04R>WVVbx#Hyk%dU9_@hnxWoqn`wWxEaCuka_%1)i{;RN*;FCJyqW z4Kg^TvWEt%j1s*^Poh?*!!nmK)fy(At1OSF$~AKa*=%*AO}DBlwmP2UDJ&US?CP$w zKz%a0nz+uACd0bQbA{q7VH$Far%*kE6G%eeOn2LYY`Syu?u&|*QFY>yDgw#AkMcp^ zb1@I=33fS4Ch<O(k~u(i}(keRZ3PDV3RuKr?)nPf;%31umW~C`(@K!JYq{UtEzE3d`Czp?_N^2h~+o zoc$&hXLI}UdFr0%r<;Xh>SmnP^U?Qt6jZTbk+T?H7s zqeNW)IqQ-aaicEC%b1oo7GmL{kh@Nz-xpKorPprz!Ic!6clHtPvn@JgUg!#T!ea61 zShlLSXSw}&U#+mmtQc@=CY;Jy12YEDdVLjOfkuAsew4q$)(M)$EOfRXmh{D+mNE_@)NWyg5e3D&Sll6pZRjUE86=!<500H zqeW@7&LwQRMQ{{9%$;lI*`Q^$iCDOk{E`ZGE5>b3#q>f&TY$m^4{P_|VMZ`7xNb{z zlZi%poLa<2Ch)HAq)U?4X+#4|#?1 zko9$|Trb?dcs~RrqZzlH))(qS>7)m0knblrXzCRBkZsgF{+kZuQ#Snk&}$Z@IG<2U zdcS<`bCF{0a2N({%x#P*dByN%#tSdJJ15-|@_Y|deh+9+Se*~V&F6gG?jBpnD)lCL zliDn}v@*oeI+n>!m@(w+m>}WjiBfaKd=4c2h__|R{5~7GI&NN=&Jz#5l+wA)^b5*& zna`V#$bmKTYeFXgAruxrcWFumTNb}Mv*=Br>AM@PGTg&x2Wt2OZQFZ=~G%@$(0}-bwML1}N|~ouXNp zxAo_Smp*axzEEQKwk>@7TjS#GM7p4dc@1vQd}!-ur9iC_r11#ZXM*NHzW=0rfL%?= zg|0V+7phCMc8q#D$#2AFcC~+#dUH{_;%aL$+d1Lcgi`OZ{83+F&g8My5M%6IP^>Nm ztWFso?^2YnkqWZJwk7W-bh(XEuxjDo#i|4<50{38g}kpa-Q+$6Vk=_jsXO^QMkhmT zZULU)A_prkB+*48%RHC320qK&+xJl0K4rdJL>{97UFZECD9pL2L8YK!Tr_|9aA^)> zAJauq#72O^>81{qXT6Ooiv;@Vk#TDxnfQ@&fkt~^5AavZ}& z5o4x^B}(vn<;&dXV-F~h3uJnc=Tb-y5Q> z0S&9$U=R)ur+4MJ=!bFX+U?AIBy$8w5|FtS$XScRfQE3c(1F4Lg?VVu^BHma+INdQ z-ZOPa7Ows5-VO*3Iz@haV=TUj52k}JPI6HW4z;JMtur`j?A&EV9ek;By}jtj0nLh} zb+(^wn-Tj4jQGBstdzOkSMVbNt0DGG(+=UT(GnwazcLwOuQ>(oWcpCJvP^keINX!GkG*uG1?er%?iHqArrmzb+rQTP_gfE+j z>|VE3_hlhHMisczft6Q&S7jKhJ_RVG&qDn|7kT?_?QKf>kAqp~a!qVRfC5=C?DisF z6yJ?j=Af#50@^(}Q|#Xueu#CGdic#z;g)WLg#&e9mUQ9>AuRX4Q95v0r9Ie4CQp~{ zGkaS|til>RP809!kQri|7U1tWP%&vDsX4o-k|gz@YK#|_CNiTFcE^1elHW_I$rfw@ zY(WobZ|_zU$ImQ?%E8=-BGE`!V%!lx_0~$J3us0Ak#%)aNT_}M^gpJm8tM-JuCeH? zC;0d4zr5DQlf((XYvlg?8$OaLCx#5%ui~%WH>@Txc3aKlt|mm4 zW;IFGE6861ZrrT_m(E~;idVd(6@HQnyN4F?pt3u!}?M4*WP_-ZdjF41O4(YVne_)vE|_Nb?nZ)t3of-4>sB`rpZM5wM!0K^_ z|A5uOVi*8uG1p~Fm1@sQ9ic&^DEvSqAk;4WSUY!epuGm162J`O@4R^~-L&FH)|egu zeXWSoYrM4i$zDM9Q+QNCM>|(Pqf%W&DRVs3J#3k7c^t>;UUyNZchqFey(KS1P^ zd2TuG;e0bmF@a=e}tU^tbcVNNSMlJS*mDcEs1m<@0qQ<|xO1Y5LeQ}or@Hp4!68&zS z;#P-`pc+xJ`Li_YC<9h6+SRk6pXpZw{u~b|)Z&^~v#FU=I%l2R1{T*Q-CF|uWh^)O zPd)}icDD2pKg@L3S&vhP$Rwu=9{&DG?3s4y4RcDhlgY4YbqqsJ>-XBBK}~;ZuQ(KB zWYEl4ewd%7Ct4YB^q(&=++1eZ=x~qAy@Lzc9|4D4G9DQ(wexQ^78cDHVyl9YQVlC@ zqz-abR!ifY?*giq`J=@QeQ!`H*Eg*F{ZBlh5ng~}bv8ELmu`So02Kl*n*mdd$m#Xk>2&0S8Gn4stxBm~ zgkK+;wB#)Y{4Y*ee)IyQe`w%^KOLJ=+vscEk&Ywx7>TNK$7pay(t!nd)pN099R-c_ zEKq;^TAx?XSUXLGppxly43{X{q$%VEE*AMLigv@~0oEgPsQ_So;m~P5&}AB=nhO0W z+Sihc`$?juNcbWVEh9BTS0`(WTUitmUtMUZb;e{xOm;n`eq zi+#`9eh44ry!6CIko!beG@j{^l!X1^la-yp&Bv1mryeT_ad0e`&jj*oZ4g zKq9B?3r!}Cr;odIddm#uZSO<=0PATA=H}Z4Ti3avY|k9`Efdmv4S3A3ec4xlgF)q* zD#%%>F}F1zN$gk_FFWMnKytE^;wn6^mG~adsVI)S$sJ90>65qgx2g8VC;VSR#y_%Tnr|Xh}*NLNRvXl z*Q&RFAhK@7uL9Iw2|!@qW`Hu>SayTSc8t@V?;yYpkjZHJlh;g0^muzJq+vTv(p>FF zZ^2{XP@+{51vcGK3A@275;jO2$vyMxTdCJylTg|TCyN^wEyawm|4RM`%d0AVNHh(C#eh zD@XV4aE^xTlI(KBs|-#@x}c1_6Q>9%U7JfsQ1>3`MWw;^9+UXZ`crI2`*DF6H#@0X zf7W!6&388Y`<($Mb827AcVq1H?xzbP%!RnT?J_p&?aw%?l=PAx#W+6p(pAucmoCVB zSs)R;>e@7g^vr9CNB%e|4wmV+kAO5^$!@w)v9VBYAr5{8fcIs(ps=x*+z!&-*_J<(yF*m*VJ&j@C^K;U)mZG8uNc zQHSJ!pW5x8#t#{??E5DC4VHnf$m9CZ=#vg+aOSLS0gjNtA*TFPw_I#Lef{RF)jvgf z-H9ybDRzGbwRL){(Vw=++y47F&Lc>QZCmWA)iqd1v{{Ue zWxIbSI@CTA2{|11pq|j0e2%mgNlJ(vN3J_pO}-f8WV>;E|cdyt0(T8Ylf zD&?v)s$QO9%DxqbNFUm64eE~NN9#=`ZNy0W%(|oOXKn%Fv^^6Lr#jK0!yg2zos;p* zd(Bg;yS#veFUp`c8rQljHBHePGS0fym2Sn7+l^O8!Xo!Dw_%Qg}9u=VK0?A+1(QWQN(vm zMAwXn{e~gIN~-~8y6$9-13&O^_|ZBR!0VNrmBjQ-nR5u2>;bYcOH4NM-rTzGbY8w?w=q^*$# z**yOUHQ$2jxB6S|)Ko1aHQAQp`c>|V2Cs{(0HJncEjn&&so-9hV^xnxVLIuwm~abc zWoz;1BCgJQ!TiE=cfqH3r&^kUzj#Ii;O8x5e zpCVJ697yVNju^2R@<&Zxl~zxrnueV-%DVVrA!< zp~?2_s}nW)T5-o7`mIZ5aR*Gk57jX1m7IHT;?upXPPh4n8A@7^FWOwLDKS@xIeS(K z9zaJULipCLna+v0wfW#SFlfD|ImAn{*7|k=7DyMUR)314h-p@Ut?cyyJ1|~5M14a( z-z|;aSJo$8AsHMjkjpQE7xS`QO^=PvEAJdTE8P~7JpyC!cNh8MGNt2~p>DujyE4;_ z17<@mMwxBWGP1_@;cS)gJu zOw6?3%bqqi9S2IL_rRDwb80_75C+3v(J|hsm?_@hRs;_=^QSvkRxGay%VeAvpzo0R zBm8{2+;Q?<&Y44HMM-C&2gepAhN`}xwrZ63C_Hm5F6xc>vi4Au>4H+y=yJD7d!5@u z^u($mkRsRS`mJ6T=m^{5EqghAZ%`_p{d+W|Nt8$ST47LEJZ6i5Qe~uT*z|*2TkwWE zSkAEU5s(|>a%C?)kW?sp&so~Vj*`i8$uH6K$Y)RGYm$-q$}fcH=)4lU(u=ve)7Ty zJr_=8hF6Ik7srmt)5S^~l-{U6)tpGH-NbXeKa2%i{$rpRA2?<9H@s?E8k_IaEB_*| zn!rym^SvIo5SGpNT?hZKDyp-SHqN7-nrX^WrrnF}fO8Z{-sAglM z&dcM?eI7su#~#Ys;BPb-UI!$->!D)K%8fT+cW_mzQ=sWhj=e8jssj8kKQJNMt1fUs zSn+@(Bn=)K)*^zo?_vxRJ;%K!gU)EmOfo|?O+>HNpMq8trgD3)7W@A!Wy}6L7ohMu zJijr(-`jeVIpS2X^Jx!Mkb*{H+QU{bHRTF?$({HIEUTbhdkHVKh4*dzh1{vQW6%Bz zIy+lrN-A^s8}BmMqy;qOZ$z~jcvOu$!etF6)!|Mrems+Qw?Nb0NoiV7dTe zs0-%Ofvhe8(3KPD*xi~%p@m!gK8v!X9w8D36o>u$J=RyzYhi3qx$Tqm~tIXFyE9lj9KY0q|=K^|tH{|6jZho}e)OrXiQ! zrCNFxRdpT$N!d^xuNjA|7777};#qZZLW+s2NmjJ};4kI~q?Vc#D1s;lln2cFl;d9u zqWn1)mM<3B>xELU-tO%tCL(;a65Y4XljiMYKLENiS8}Zd+Xd*#96(pwn4vLc8SW~Gcv5@8`^@uGjT>6&D75a%jlNQ9$o>{A7(P2Jqd!(=$ z_J7$CA;J6_n4TuUXb|?Bdl{Ab)HB)L`=>bws%NNz`NQH@&JkGE@T%lt$rt< z(q9Z9S1MVx)qcb95>di*CZ?~${RsQ<2VsMgMvLn+W+iLFtT$w$uN62>wyE0XVb$OP zI0WL~CRX$J;Pxc|T5Am-$UArg1At~BYR29EDl_fXK!${^Rh#@+h6gmO6Mk%RF(vqt z$OFItFx9Wo0vfnPgwzPU(lQcC4IBIVTv?w-oa8rdy$qPu1!2~}*yaH@)?|Jl!x`fp zVK6YnS1Ahw6MkvaUu+t=#MO-l?$vc|)MPMImZK&jM{ujhJNNF|hGu@?|uA>0iV z4A(f{j;O9{HASWk2Z9SB4?P@3p?t>N5+S#Ao~SB@i)0FI>mIMNT09Q?hUxc0Rk0Bh z#CUH9_Nbna-v0=<+K1L|bP)p&l=ELQB5WQoN8#g}7LC$L2PWYaBjRiaalqi}H{m`k z*5X5Ew0H|R8edb2o8!yS;Lm<9y>g8EmJF!}J%k=|o83=b0Yi2+a^3|N>NUU%lN}SA zwE`wr5~kAJt9-{Rs6q+xDKiOSq<3egxg>PQXDzPZNKbWVb;kFyp@0qtG;xYpccba`jwjoiYJke zWVB(2KaV+9xxJcM_liV*+4E7z(k~6#Cv#MGSlG>cn#!q6@?ZXD+B{eA{&UAXz8H7X zBHe7Dq&;5-5@HRAE!x?^Ll{iu`8C}IOQtMwpA9zRoZ_xZVt)~Eh74^Z$J=`N)#?dL zO+HBWwGqGF4ns`2Rh>*ya3qZw;qcH#xRPK@CHK0$3dE2xE9F#|9y*_6E&6kQ(jjU5 zll$Ao=eaq$3YO?wt&1yHtuI7bibavOgfJ(go$3If<=PB!tXS9H&Z`dEjAC?ak?s6ghWE_?iueg@TVA1V}CNH0%ZkjKTdbkTp#a6&3 zOM6Q=<9~Wap=mG9tHMHxa{Dwfz@?Ib-3QpG%LHVG4jObwH0Hq$`7@?UGjZz^x$6$5 z%LV=;ph0ggpaH}XknixWkm=++r=I9)F2@fT<|AFAO;1N?Pe(S<;S8jv^7Gz&x=sV^ zqp$vq&-RR#K%T);^rY}vR{wkAL3^@(P-W(%23)t%DmQMe?W&A@O%KB5V2SuP4OaZh zn;tiVQiCXc1oapn0m6M5sNXw-G{6egoWnPrzr8GHz`uHlU{+Si0#=wn@Beh~$Dfmf2O=@$0N ze%8Zz8kPqT`kH|AwcSG93Fhug*sty5jBg_dW{WUGjW~XIq@N!@yXGwROxb+m?(u7RZpBr24j3!f`5H9B07?o0aI4 z`>q9o?q(QuP*-l2<}8Kl_^iKmpTcno%W=9*HZkQmYWiF^`G&|@ALQ?zUls>I-Mh!e zHT^@*#2Lk|r3#OO5Z{NaJX35->7>hdO7qxgdF&zg%4(bJhH2u<%^sK`1YH;XZGkec zEbQ#zmN(Z3iw@&OLq{3{8Vu*+_qf@UO*~K7c=%@?n(jM)$L>{Zc}t?@qCM=-n_f4+ z^230wff=CMt2bF(DPy}#8(&W7H( z^w%x+(}o|7y*f4Xzn`6}{7=9~gM*Ytz4@DDRPW?($*8}jT`gq%rT;*?4z2}m*nLN` z9Rb(+aUVdHAOKa+g9cIL9PIEdyKUk&F%c`9qG1QD;Ih-+SVAw>q@EyZ+wjD=eniRS zjefWOaz}()UZPq);w@Z9ro3j>#()ju@ZP4?8l8LxVOu>U&1H=#BHA5-z+F}R10&qU z&i-J8HXKmkBZNcyX;-5dj8*oK*Yt_n8z2;+N;P23X_D9$ob%Tbepu;GxKQ?<8vcoU zm}6#ABR-GEcN&+2Rz-%Z$>mBeqAq|`zC^!uw1k1==fNUGSg92T0&@q3mLF*CA3)x$ zEImJ*&FN!Moy7tL4%WO&0DtYohGm8TXS@_Ji&U&^9kOkpGQKLuFkae4)Hycbq3qAM zt|_fCM9}Il(cnA4F5XVwE=#-K#RM6FoIB)D`8#^`V9BraD%*9oJ@Q!J{lW)VPLmYY zHdU(^d7!k-b>{ctJ|$n}9-7u>;37ji^|tyA-a`Fl`e3OQ`pRdO zx-y$&a}Nk>AEoH&rQk6az4@%6c1}=r+dL7uh3^Rd3k;q0GXmY2&Cqk_;X{XItYil(30?CR-s zY-&UdHpa%075Oxb3SpF)7)w-9$${GFu zbonuIHy7t^eY@?)s|~M67~O*9S<)mTJBA;Gp=7Ta*58xA)Z5MjOLInntu!DZ*%!Xd zW#gdC3Sx#o*gWs()CwCQs#z$-ZYS!3d)vESpVM?^j4~P|=Pc$Tw$SF5w;r(83>zu8 zGPXg$vTl^2T0M3H{5ckYmh`BlqHFBUkR0SfN${RRU-Pz0z(sf02Q9hrh1|lVnB?BV z3rnha0Ax8QHor!%V|O1ZXC^Ddor(cT24QUll7O++Lj-%t4^7(34P=#tpp6+TD?FeE zI%KdDbA2UgW-UU6ZZPkJ#!9Wng2L8ww}50{pzp8Dv46w$Oj5I5FzHrrJ>h*+o%e=7c{OVcpBUQsl;ou-pr#6mXyNw4p z#q9gIpE07sR4`wRVMkZEpLg9VQ|uUaPJ&S)lL*AUD7LCF9i!M6)F`UGhLff zq>mVM6!^Xa3a>bE!Z)`qhw){-8qT6JA^Qs*B@g7bLAjF1$>4kA3J@L=RjQW`rJL@= zZ7i_wF3F;n*cQMXrZY@PVSySHvq0OY0~%i1jwPw>JEg`7l7~W>;_}|N9V6<4j)x$C+367XSOC7++d;5>YpH!XM z`hd?-$wB-5CfCdF+WG?z^n_ldQ}513at@0}6!M$#-C7}3MD2m~V4tYeD6^mVpQ4Ph zC1B$aNdCirW+uq8(3WJ4R{OF#M=VM$sv%3B0j^M-_n_rzC9$ciQtlsmZQf;=zkG%K zM!Y&KFzA@%v4EV49e$5AeQvSH%vd-#>vovbEwYy_qnH6VMAJ=c0N-ev`_<;Z&2UL= zzXqn!)UL&BkV2XidPR=d-@9iTeS3Kjm_`YxbW5)s!0wge@%v9@_LQ?TGI^3f`w)SgQb2(a@oy9ugx>WEj#orw&d*qU@Ywf z#!~j~bT&D(Af{;a_q5^gIn)xZO7+GWl2?$Ixb0#roH6;)-3KzGYI?O_0fXN8Bm^ENQN0_TZGQR;7@q zzKN`N;qT*P!+OL+CY<`j@nHc{k7ie8yg?So^B5+25sgq_y#togLLlqP?nvyBqrx2V zR*K!;{}4cfFRATkIqf zc3OkF`ZDsgr;;}^fvW<02a$99uGhvhNe3Q-H{BIE41fsy_kvi`HRf|c)@Cs>lqCHe zu5Q+7Sjkzt%kkV%Da-kF4@a2?^>s<2+boAD@53s-h$3NEo@Y8o3gXHJ51R^j87-5x z@+l75F6wO{VpEJ4KK>vtXE_+?*b7)K#n8-&@1l$|kKHu;sUa}wLbJ>ND01JdE%dw? zU*iAszcTlyq)PI9y9T_C1nK1cKa6KDu@@ zAcw99*k|Csknzf`_<4t1{2UAg8BhZ!@m_*JXoLSR`9K{hE~{Qq@04(clPWW)YO z=Wt*GEGB0=_>k5;fbXsvQN7$t+cn*q)78g6xB!Qd%T(c>pKahlx_4`3hPUz>C}~5X z?$lvFGOUM#K{=cl2l^mk1zl3zXx ze&IIy=T%Ni?&&c4P_j@S$_wrA_WcB}-@=yD31V0fjAK7!? zy?Na#e{_pGEe1If#6x9fpY9i^Wl&x3Yz%?*6h5((%Kwn8N}cAv8OJXVinLc97TU@! z7NwG|GfF%wHi&*)*T6a|U2?L#;v!Hm=ixW@_{fqefggm1P@RI1%AN$c{YNsM6Suze;A6!@vJvSO<67IMQY^Sf8oj_MkEimr1sPmjrzBL4DkPjiDXR1mRvt=h9v)j z7ym1&w1gC@0Di$G`rm)-wz}$FhjG3kv}>%Uk0f>8a+J#US_3wtS zu1(8MIz#R~@W2Q>{~}0S+yCLA=}bY1U%o8)-F-E%CwjkV(-0cGpKWqMoP`a1S$P^aKg_O@sHH(CAuKzUGFUH zX;VR}T>V(lS#l4`9L0{`F-RUM``s=SUqFRXEV} z5Xi$F=5iqk-|blju-T1Dr#k#0n<-Gj_15;lAOv<4jIv*F;eN`NEtKOwv`>;V!sM2n zoO3aSodTXW4A>VYo+(FE(=?||W?2Na(@O%XZU)6Istw$tX@VS>+a~UFvVYhnY1D(b z6ugk*l3Zn)=suh2?y~$CwHa&$kda(!+wQ6)2+U|^CQixbIwQ)oLHuVKB=!Y57-rnJTHVo(FN8#-Og-F3}B7Ezt&mlz0siETSMmE}qpaE!0_m4|8USuByC=rPD}SFkGD#o$HesHwu~V&M zw|yPBWTPaP8-uz5p3?)XS4>6O6|&K%iLqqIy}boG3(B2!IZXPr@bimRY95NH+qdH9 z%XKC!`gE=IxCGcOUY@K?r8tjYV(ceQk%z^1o50RW)58GjdJ#`jAZK<+yLT0-_<40H z#UY(}M)AS@N2um%J_;DC7y_&x0Nqv|7U>U94QC{AdG=yaS`l=@CE{Jr5om5-pwN+K zKR-8E`0+f#8H9l=e(dN_qV6x;LvfN1N{UPyX#)E!O3U@7cO+xxWM2Fh*QEw!-Kakhe0{;DnKR>mQMDkYAu6Gvxmo$kP#}8jSX~g6U ziYkWWw5h-Xymj>y+vV3i@gh{$EuD@ZV2Z7kU<+p?HiP9hF#&!M1le~blBQV#x^3+E zuvSq$EHPt0{D%J+x)*)%l%n3b$AY~sPJ#5c)tcVK4sme}s7US@_o)J=%uZ146lzug zui>hFG;;j~K0g^arXAU?N$~^es9K7p#o){veZA@EbJp>a@**Q`2O+il&K^3cLhMUP z5rs3dFvbQbNIvB1H4sFeqZV=rdKDyN<+FEzR;HbcZkVy-2arA+^G+^MlOiHEbJnwySwmw-2*zGb5smViH&hL z9SFQQNb0AuG)lOhUl(w6p#QG}^eSObkF~#J=&E;n^zwW6D;i@X&7*spJ=vL#OL`CQ zZBGO?s0#D*bQ!S3=B@PN|M_N5e=0!l_5a9w^LQxtzkj@}A#%u4*(ys>mWswsQr3u2 z7(*&$mz~DGB!w(dc2OjI7~5bZDGb73?8;aNjWHO^_dV)-&N=t}x$paNf4`64U%%_0 zbL28J*Id_odA?rHWk~U;o>i0Z!54jJlC5DD=H~WdY?eQ>(>)Gw9;C`7;{P;(BJ0}z zLxWVvb%U{%x(kj@6o}TjEnr*Kd8>&_r8sFoH*Q^4_F@3EAjY@YIXX<^h2o){J+7!& zjC)UwLp3Y9`M-97S|Tq|8}qsaX3OsdRP)ESF?Rv8*b_AgTrURO?lDFB20)C&*k8Qp z|7nzR*%em8jx>@X&xhTmm@reS1*mg^Fpw87T=yEBdtz35V zp44!#acx!~J*Q3Uo*Y_t3`LcBjRg`>#>IzlWvfIH|yXOiO9Z~ie zxRtFOwO95coE5At_D!Y|ALDVQNB&n;NxNrrGgu&Brk0FGnVV(G!U2xh8lc~+YdMpA*D+oWfKzL^S}(IlYrC|Gvw zW{NBXs}jxwHF>4lO?63;H&-~=zYEa4g)d{EEYwlKA%FjI-u#a$G^O%ebwimbU6xIj z{56;-RP|;QMMss&O(dHsaM!_AnjU&8&{R(Oi3C1i3%xC)<>k)X`4WzFM?~8Lrlw~7 zz&qdNgMFMhhaJyIYyUsJq`d#`B}MI(j5~FM$F1Hzo(-!?areA^h=XlCQXq8P%9ZjV zZ$ruuQF8TTBSwf??2h~Y6KxWU32T9-{^bY`jKSG%&pC{9vNaCB+4Bhh(<(d;_+07Q-;KZ*l4w;^YKl}YUd;ig)lp8Wd`tNSA8KZJ9 z;bs5vx;tO94Is00m!R9Zpb>$|t@_7nCMMCQyRL9D)EJ3zqT>Da=VSlz!nfpCm?BB_ zIOU;BsNlc-CUHlK<+*FVZo1w|rf!2M@#-%F^j~M>z31Bs(}3=Ml!6kETV@o}bKnH^ z{H|x+My-)5;=zMrOvR7MANCfbjj$Jem5ui?=i%E}6C&+$&8;CL%r4fxj#97UkLM|z zAHU(WGkyp??-}#Wi8B`WcAcv*5=^-#f!n9h5o5E(ot^ncD_>b ztao?)O>Mtpro0!$0kp3h8^6W;7@&wWB0slw>En;OwdH)jnR;$9jlZZ5r)j7&=Dx+{ zUBsr)csjA=1`1tN8@5bOB-fgSk+{lI78C+9-;HBa7cA;`>&6qf#V03{M;we`?K!l& zfpv%JaREc*Fb#h!mQum?6RRD$qYL@i9&^h`RQYOTb)0LK;VHt4q1z`L^MTK2G!URy z1V3vW)H<_1db`D?Q8DZ2U^4sTAt!8fDyTT}{8FFCOA${fc|^+hojWO|e}=k6{vF#Y6I{}CX@H@|PZ zv2fyxD4*kVj^osaSUXvC#Y2<%A-T`t$*sp7+y_+2u+-I@v9qT4qFH(NU5@6daJ<}83Fu33ByVCH)lD-YPWBrB`EbB zn8qnh9mtJVE7)e1Ek_=Scq!X*?wpdgXds?RL{Av8a5?bWorRaZWeoXXpqT-J*Y-Xv z-R?taeN?t2{u*PWAa#%ke>o%VNTGE5Roo6q>k1ORdHD|ad`*+5A0J%KWjKd_`<%co9GF)XL(aN*oNdR4Fwr?dTx$PudA50 zwJ5XEZm*Q_YY;7^2ey10DWciz9d-#H**V#lytE;PrxG`HMWr2+@P2tLaa0MD&V%3# z^DH1>st(eoSF!@iSuenLtolfp4avSNbhmAG{xF?}8@Yx<>`*+`pKt=R+NOm%4*TvP z#+*MYZ|LV7u33$-!y>`Gvy_^Yzdg>G>ps}2>AH*ETJoyHT}528O!sOu$s|sX`0o!t z*Lu5H9uD`(n{cnh*rgWKy}_TyxZOKuCbgv$T}<#gJ~^SY92UwH6FOmjpx1MbxlfUH z)3Si_b4=W=+hr>SXvs|8p1IY_--NvrbfivNR54-$GRINn#Cc#<T#%8wVPB1A)0bFtocyX4;(7C&;+ zuInS-wOT;1p{9y$j2~ty1~j~(TV}jOy{_@FP`=CeXl9i4A#_+~?|otF{&jla*&^S4 zEQ$1v_h}D%6Jxkmzm2_13^XHi1$2sBj98J~ghblfCzs2CMJUezkRuf5D?={V^s&6S zbF4W;1`mXczAlxEtSgxli7IL^+%G*5~kIW^X#Qa`iC=aR03!hM@DF` zFSwq(srdzhn&dE)w#_!Xbl^yPMkV#%>evC;NZ{e;YFZ0K(u>w`b2<=z(_g^{*8QYnRuNXi+65`Jg zeFR<%FT4tbI$mme3QZBL6YPadR~fg5cyMIbvx2Gov=rE!OU?P!G!08iax~vJ`-5A^ zc1?uw%<3+v8F*{&@dlnOowU?rbAdTa`Hf}@KG;8(QFojwkkZU!^IGmW)0Ow#z@`Ux zHFIeT+<}v&`O%LW@-maVdb~!9-ESs`D1iM~WOgFbATeXP2Zw+g#HzT2q5Yw#J2pTl zi!e%aJ+;Z=&)&4`k7`!pq0o=fA#Jjj*2S17fN-J@ep%gHoXIAWa*Ut=_ZdRPg^1~QIBDvgJuq9)= zU)1g5)CoAW)-=x#|LQB$n0n9jAzJ(IS}wu{7lgdICm34kv!NwzOa6ImNTBV0e1OE>kIg5-wKRiDy+F*JpuimmjI6iAf?*sCXYMl~u6TRbW*k?y5Y=kQx$3X`%^S|ojLDRQF7HI1 zn3Og>UE31;a6&i1mE8&(y|*v6oV(3^g8ZR$42?W>_{-$k&0PBMOY*0FG>P$jk~C6H zY%y8CILRKE7Ya7W>y?Qk*Hlf1_D(%6*$Y)U%=|;>1=xl|wncV_gJ8A0m5fg;*JUTI zk*N4B9j4#uLL%bfuu-rn{6>WZEN1H}lM|FG+IEItoBc%m)UTg@l!6qW{|QHEfrayL z=NxI++iw2#FX#N^+5ZDPVJumzECKZb>QTx|t!seeZ?p`>IHpM5WXXaGJ9O0(Zk6h~ zItLI(LOsX}O(J}fmWZsbOjQ>SW{NveZ2q|mMUX(*^4%GS1uiGs+uj}BZX6TbQQG^1 zL0EzoWLA-Tez)$+%ky@6=hTy3C^&MTsuhLQX$$ozpUhB2z9#;1oB$_lLuNlVy^d9R zC*b@2xud-2e4^i^89FsL1U5DjaNZAJO(8t^t$DNDpP&QTZqM9~@zU<#m#K20N5t9*bw#k8%<^D>H6;=sJ8uMoq<{RjL zD5}+0Wtu$Wr|bY9qAv&lCb;T1?6UPm^Ug?Ax1wlzQxv=GNzB8 z)Ltx;zLyTe=uzpK1GF|+-6{Ovqx{SE)B(r_v%7fcehPj-34$9$$)iM^}I z!Z(gzvSX(SH!g;2MAJ* zJiw*AUUDprCi)(gC4A@?OW2O~SYX-nn~Qa=5#s^gYO;6Tq<_IDr(*r%lk88ACkP#Lo~RjZ?{exf%!4~cwebR* z+O_Ir1rmMa)m?X;J6*98J6HT%IOd1Yg{$bIc40n`bCWTOc=pCi*LikKp4hg<6nuMY zP*^?gCw6Q1J7p_jKNI7z!}E<}xZSP#ai&NGcH*))qMV6QS0_v@HT|l5kGv?~$$gQ! zdrV`cM8GuOXH%s%2rR&Oh?0*}&dQCP5H**_(tDPp)!}n*Q4{9TZJ@t?TEyK_;l&xV z@ik7z3p!-yOyb7$#OG<6L~8iL9=N*imh;u_Pi(~zT-bnQQ&Yp@{InMxu6gJakt(Fm zLGb40Z~kWuSvZ;S_0OsfEga!@2nrT`?m9R+D%-w)NAV!#N8>h6vZyIroHtj@%!+9Vg;$fuX!nqQ;H|2U2oRIs$}1>2A*8o+3`BPZ9L zp6|?wb1XQ^359&AYQbKwhf_iqy!>}?K5x*s-#h!Cmmj-{Zq1O7kzDyqf1K!WtVS&l z?++3j?ju+}Gwkn`zlTGscHPCj)*~4QSgn`1MYJU}_@y>;<%{L#H1+T9Z*Zd8>(MFR$pvCgm`#EEJ0!M~m zZO98wOzymuqn=%d7g^X6I{fQCrU?QphkLQ+YqdHr>leU~^s3JVvcbfdej%Up;FkB1 z?XZ?cvNz?L^2P+PSS&D}?^ApW;*x~%C)jDae(mg{IM^=z%(}gGAf)RhD0ke!x;DEU z9*?js2%R%+o%cbs)u=%$Z1Xc%KAEd7U&PXl_heaeoJ=vcXdHDO4D9F%OnE;)9DCo& z9>00Ze|0s9^tIpNnWtRIzT({8Rt@h4ucnvWVt+Uqo<8en%#zp)|G_Oy&w>M*y@l$y zXj~7y#$^46PXGbFpO{*ApZc!;(4X4a*yYuRd~e5G<20;oBgz}{3DbG_!>g7o#up;Q55pCGCPXBV~araP4YfZdG-4|J*mb83Rbl1mr+5=YDImtg$1Xaaw`L zc|rodT|GBDx*VqQE=D>=2eOXeHzrD}cOI3p$Q!2CaDrhI1<+#L0er=DCH858YaFh0 z7$Lj-Gr*U?V0~MM=MdGkO(yyYB7;?YI2yNAb3;ubA7GR2N>_op2CccWeBl|Hd)TzI z!>Bf<)+NPWu(^&dQ_p?IlRWzTz)T-`?)L8WLre=77@^IGh~>E&@8w?BUkwyIb{zFRthF^aDfuON`$_K(t>L^Iv8wIE8 ztr(UvA1G$J#-X9!_#edtbM18VW@H5U$WnD5tQ~pdyZna^2cn#aqOs42>&?XCBYexl z3HUpXbeQ{{&gb!)o_kliLwo`n?L$pi`bPmPgL}mv`KYi^IP$`z5MB2rX{`LU3VqQ@ zp^j%C4@GoRsu}-yru%(8uz>k{OKr{00ayYpfz~HdBghYXtfWX>wOXLmQe z$UC?h-<53{H>Bwzk(peMBtAnmnT*A*1vpH~58KCv3E%0X?n(tI}7kk@YDV)g||ur)34C>_K_+ zU*;hk#n*n(?u9=y({jkDx$OaA6|9Ol9x*T9!5`=i)%KLb^s2vRGbE=8f^TKY_YYl$ zuXxLYu=B)Fg1EiLqsD?RhHX(>rht_t|(CdK9JYNMLztZTBcrNP$x-@9l=vJh&; z3&i&d8*Do47rXu=S~dziZa@XgBBa+Dwk5N*>l>f7lN6+@oy^*#kGedvBn$pwdqJJf!PqK&$&v%zoFqiIM^w#17Q)%E1pTw=1-GzB;=7%}nrqnc|;T zB_b@|W?~0lee_y5!N<}@C;Sp~c25GJCWBLBU+DuOE+>s(8hMz9nbY-B3bbYtf^;lo z(LOG)YCnTb5hRhO!^!cOEBM^AgBaJR>9*_Sg+U%)ws6vZ8~llMG6z!1Q~A?}p=pye zI%(Y1?XliRGnh6g#LlxFdPRdDJVa>B|t?1~!(rO*&w}M{*!EA!vJ@7iNlkTP#6;|d3U1L+CY!l|&RCnF3ZxTxai>_q? z>Xnz5rG&BA$P_+lwvK$N96(({e?*^|4ghZKbKA@y%+nD_{G7bKF>57_4A+f3Mne-E zo2)rXq3�!R>ysvC?1io$BvvPZYK+Qy%6^~w@h_PeW#FK76f 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 Qlev*P*r4_n@4*s&1NcyeYXATM literal 0 HcmV?d00001 diff --git a/Doc/Window-Build.PNG b/Doc/Window-Build.PNG new file mode 100644 index 0000000000000000000000000000000000000000..05cda542b49c7c6afadedae1fef10828f7f906e1 GIT binary patch literal 23271 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 +
+
+ +Choose `Release` build type from the dropdown menu near the top + + +
+
+ +On the top menu of Visual Studio, click on `Build` -> `Build Solution` + + +
+
+ +The executable can be found in the directory `Build/CLI/Release/CDBConverter.exe` ### Usage @@ -163,7 +197,7 @@ Usage: The following command converts [San Diego CDB](https://gsa-temp-public.s3.us-east-1.amazonaws.com/CDB_san_diego_v4.1.zip) to 3D Tiles: ``` -./Build/CLI/CDBConverter -i CDB_san_diego_v4.1 -o San_Diego +./CDBConverter -i CDB_san_diego_v4.1 -o San_Diego ``` ### Unit Tests From 6f7825bdc33a4a2dcd2374b0634af0d95391ce5e Mon Sep 17 00:00:00 2001 From: Bao Tran Thien Date: Sun, 29 Nov 2020 01:20:40 -0500 Subject: [PATCH 16/21] fix image link for window build --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c1a2769..d40a317 100644 --- a/README.md +++ b/README.md @@ -152,19 +152,19 @@ cmake -G "Visual Studio 16 2019" -A x64 -B Build -S . -DCMAKE_BUILD_TYPE=Release In the `Build` directory, open `ALL_BUILD.vcxproj` with Visual Studio 2019: - +

Choose `Release` build type from the dropdown menu near the top - +

On the top menu of Visual Studio, click on `Build` -> `Build Solution` - +

From 34c8bcc3dd7b10d009449a1de783e0d94573f94a Mon Sep 17 00:00:00 2001 From: baothientran Date: Sun, 29 Nov 2020 01:23:01 -0500 Subject: [PATCH 17/21] remove new line below window image in README --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index d40a317..0bec86a 100644 --- a/README.md +++ b/README.md @@ -153,20 +153,14 @@ cmake -G "Visual Studio 16 2019" -A x64 -B Build -S . -DCMAKE_BUILD_TYPE=Release In the `Build` directory, open `ALL_BUILD.vcxproj` with Visual Studio 2019: -
-
Choose `Release` build type from the dropdown menu near the top -
-
On the top menu of Visual Studio, click on `Build` -> `Build Solution` -
-
The executable can be found in the directory `Build/CLI/Release/CDBConverter.exe` From 05e973c1e0af142e6f5eab13678f0ea8054b9439 Mon Sep 17 00:00:00 2001 From: Bao Tran Thien Date: Sun, 29 Nov 2020 01:28:56 -0500 Subject: [PATCH 18/21] specify the test executables for each build --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0bec86a..d9bc708 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,8 @@ cmake --build Build --config Release -j 4 The executable can be found in the directory `Build/CLI/CDBConverter` +Unit tests can be found in the directory `Build/Tests/Tests` + ### Window Building Microsoft's C++ package manager `vcpkg` is recommended to install GDAL 2.4.1 on Window: @@ -164,6 +166,8 @@ On the top menu of Visual Studio, click on `Build` -> `Build Solution` The executable can be found in the directory `Build/CLI/Release/CDBConverter.exe` +Unit tests can be found in the directory `Build/Tests/Release/Tests.exe` + ### Usage ``` @@ -199,7 +203,7 @@ The following command converts [San Diego CDB](https://gsa-temp-public.s3.us-eas To run unit tests, run the following command: ``` -./Build/Tests/Tests +./Tests ``` ### Docker From 343fd474c56dadca317bc94940d2c4fd88ed264b Mon Sep 17 00:00:00 2001 From: Bao Date: Sun, 29 Nov 2020 22:21:12 -0500 Subject: [PATCH 19/21] remove unused math header in Elevation --- CDBTo3DTiles/src/CDBElevation.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/CDBTo3DTiles/src/CDBElevation.cpp b/CDBTo3DTiles/src/CDBElevation.cpp index 69415fe..bfbb8fb 100644 --- a/CDBTo3DTiles/src/CDBElevation.cpp +++ b/CDBTo3DTiles/src/CDBElevation.cpp @@ -1,7 +1,6 @@ #include "CDBElevation.h" #include "BoundingRegion.h" #include "Ellipsoid.h" -#include "Math.h" #include "glm/gtc/type_ptr.hpp" #include "meshoptimizer.h" From eec1c452661a1341ac867244580511efec7741af Mon Sep 17 00:00:00 2001 From: Bao Date: Fri, 4 Dec 2020 23:03:07 -0500 Subject: [PATCH 20/21] fix tab format --- Tests/CDBElevationTest.cpp | 367 ++++++++++++++++--------------- Tests/CDBGSModelsTest.cpp | 56 ++--- Tests/CDBGeometryVectorsTest.cpp | 1 - 3 files changed, 214 insertions(+), 210 deletions(-) diff --git a/Tests/CDBElevationTest.cpp b/Tests/CDBElevationTest.cpp index d84961f..5003622 100644 --- a/Tests/CDBElevationTest.cpp +++ b/Tests/CDBElevationTest.cpp @@ -352,25 +352,25 @@ TEST_CASE("Test conversion when elevation has more LOD than imagery", "[CDBEleva std::filesystem::path elevationOutputDir = output / "Tiles" / "N32" / "W118" / "Elevation" / "1_1"; { - Converter converter(input, output); - converter.convert(); + Converter converter(input, output); + converter.convert(); - // check that all the imagery created as textures - std::filesystem::path textureOutputDir = elevationOutputDir / "Textures"; - REQUIRE(std::filesystem::exists(textureOutputDir)); - checkAllConvertedImagery(input / "Tiles" / "N32" / "W118" / "004_Imagery", textureOutputDir, 10); + // check that all the imagery created as textures + std::filesystem::path textureOutputDir = elevationOutputDir / "Textures"; + REQUIRE(std::filesystem::exists(textureOutputDir)); + checkAllConvertedImagery(input / "Tiles" / "N32" / "W118" / "004_Imagery", textureOutputDir, 10); - // verified tileset.json - std::filesystem::path tilesetPath = elevationOutputDir / "tileset.json"; - REQUIRE(std::filesystem::exists(tilesetPath)); + // verified tileset.json + std::filesystem::path tilesetPath = elevationOutputDir / "tileset.json"; + REQUIRE(std::filesystem::exists(tilesetPath)); - std::ifstream verifiedJS(input / "VerifiedTileset.json"); - nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); + std::ifstream verifiedJS(input / "VerifiedTileset.json"); + nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); - std::ifstream testJS(tilesetPath); - nlohmann::json testJson = nlohmann::json::parse(testJS); + std::ifstream testJS(tilesetPath); + nlohmann::json testJson = nlohmann::json::parse(testJS); - REQUIRE(testJson == verifiedJson); + REQUIRE(testJson == verifiedJson); } // remove the test output @@ -384,25 +384,25 @@ TEST_CASE("Test conversion when elevation has more LOD than imagery", "[CDBEleva std::filesystem::path elevationOutputDir = output / "Tiles" / "N32" / "W118" / "Elevation" / "1_1"; { - Converter converter(input, output); - converter.convert(); + Converter converter(input, output); + converter.convert(); - // check all the imagery are created - std::filesystem::path textureOutputDir = elevationOutputDir / "Textures"; - REQUIRE(std::filesystem::exists(textureOutputDir)); - checkAllConvertedImagery(input / "Tiles" / "N32" / "W118" / "004_Imagery", textureOutputDir, 13); + // check all the imagery are created + std::filesystem::path textureOutputDir = elevationOutputDir / "Textures"; + REQUIRE(std::filesystem::exists(textureOutputDir)); + checkAllConvertedImagery(input / "Tiles" / "N32" / "W118" / "004_Imagery", textureOutputDir, 13); - // verified tileset.json - std::filesystem::path tilesetPath = elevationOutputDir / "tileset.json"; - REQUIRE(std::filesystem::exists(tilesetPath)); + // verified tileset.json + std::filesystem::path tilesetPath = elevationOutputDir / "tileset.json"; + REQUIRE(std::filesystem::exists(tilesetPath)); - std::ifstream verifiedJS(input / "VerifiedTileset.json"); - nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); + std::ifstream verifiedJS(input / "VerifiedTileset.json"); + nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); - std::ifstream testJS(tilesetPath); - nlohmann::json testJson = nlohmann::json::parse(testJS); + std::ifstream testJS(tilesetPath); + nlohmann::json testJson = nlohmann::json::parse(testJS); - REQUIRE(testJson == verifiedJson); + REQUIRE(testJson == verifiedJson); } // remove the test output @@ -419,28 +419,28 @@ TEST_CASE("Test conversion when imagery has more LOD than elevation", "[CDBEleva std::filesystem::path elevationOutputDir = output / "Tiles" / "N32" / "W118" / "Elevation" / "1_1"; { - Converter converter(input, output); - converter.convert(); + Converter converter(input, output); + converter.convert(); - // check all the imagery are created. - // check that elevation is duplicated to levels where imagery exists - std::filesystem::path imageryInput = input / "Tiles" / "N32" / "W118" / "004_Imagery"; - std::filesystem::path textureOutputDir = elevationOutputDir / "Textures"; - REQUIRE(std::filesystem::exists(textureOutputDir)); - checkAllConvertedImagery(imageryInput, textureOutputDir, 13); - checkElevationDuplicated(imageryInput, elevationOutputDir, 13); + // check all the imagery are created. + // check that elevation is duplicated to levels where imagery exists + std::filesystem::path imageryInput = input / "Tiles" / "N32" / "W118" / "004_Imagery"; + std::filesystem::path textureOutputDir = elevationOutputDir / "Textures"; + REQUIRE(std::filesystem::exists(textureOutputDir)); + checkAllConvertedImagery(imageryInput, textureOutputDir, 13); + checkElevationDuplicated(imageryInput, elevationOutputDir, 13); - // verified tileset.json - std::filesystem::path tilesetPath = elevationOutputDir / "tileset.json"; - REQUIRE(std::filesystem::exists(tilesetPath)); + // verified tileset.json + std::filesystem::path tilesetPath = elevationOutputDir / "tileset.json"; + REQUIRE(std::filesystem::exists(tilesetPath)); - std::ifstream verifiedJS(input / "VerifiedTileset.json"); - nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); + std::ifstream verifiedJS(input / "VerifiedTileset.json"); + nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); - std::ifstream testJS(tilesetPath); - nlohmann::json testJson = nlohmann::json::parse(testJS); + std::ifstream testJS(tilesetPath); + nlohmann::json testJson = nlohmann::json::parse(testJS); - REQUIRE(testJson == verifiedJson); + REQUIRE(testJson == verifiedJson); } // remove the test output @@ -454,28 +454,28 @@ TEST_CASE("Test conversion when imagery has more LOD than elevation", "[CDBEleva std::filesystem::path elevationOutputDir = output / "Tiles" / "N32" / "W118" / "Elevation" / "1_1"; { - Converter converter(input, output); - converter.convert(); + Converter converter(input, output); + converter.convert(); - // check all the imagery are created. - // check that elevation is duplicated to levels where imagery exists - std::filesystem::path imageryInput = input / "Tiles" / "N32" / "W118" / "004_Imagery"; - std::filesystem::path textureOutputDir = elevationOutputDir / "Textures"; - REQUIRE(std::filesystem::exists(textureOutputDir)); - checkAllConvertedImagery(imageryInput, textureOutputDir, 18); - checkElevationDuplicated(imageryInput, elevationOutputDir, 18); + // check all the imagery are created. + // check that elevation is duplicated to levels where imagery exists + std::filesystem::path imageryInput = input / "Tiles" / "N32" / "W118" / "004_Imagery"; + std::filesystem::path textureOutputDir = elevationOutputDir / "Textures"; + REQUIRE(std::filesystem::exists(textureOutputDir)); + checkAllConvertedImagery(imageryInput, textureOutputDir, 18); + checkElevationDuplicated(imageryInput, elevationOutputDir, 18); - // verified tileset.json - std::filesystem::path tilesetPath = elevationOutputDir / "tileset.json"; - REQUIRE(std::filesystem::exists(tilesetPath)); + // verified tileset.json + std::filesystem::path tilesetPath = elevationOutputDir / "tileset.json"; + REQUIRE(std::filesystem::exists(tilesetPath)); - std::ifstream verifiedJS(input / "VerifiedTileset.json"); - nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); + std::ifstream verifiedJS(input / "VerifiedTileset.json"); + nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); - std::ifstream testJS(tilesetPath); - nlohmann::json testJson = nlohmann::json::parse(testJS); + std::ifstream testJS(tilesetPath); + nlohmann::json testJson = nlohmann::json::parse(testJS); - REQUIRE(testJson == verifiedJson); + REQUIRE(testJson == verifiedJson); } // remove the test output @@ -490,20 +490,21 @@ TEST_CASE("Test conversion using elevation LOD only", "[CDBElevationConversion]" std::filesystem::path elevationOutputDir = output / "Tiles" / "N32" / "W118" / "Elevation" / "1_1"; { - Converter converter(input, output); - converter.setElevationLODOnly(true); - converter.convert(); - - // elevation max level is 1, so we check that - int maxLevel = -10; - for (std::filesystem::directory_entry entry : std::filesystem::directory_iterator(elevationOutputDir)) { - auto tile = CDBTile::createFromFile(entry.path().filename().string()); - if (tile) { - maxLevel = glm::max(tile->getLevel(), maxLevel); - } - } - - REQUIRE(maxLevel == 1); + Converter converter(input, output); + converter.setElevationLODOnly(true); + converter.convert(); + + // elevation max level is 1, so we check that + int maxLevel = -10; + for (std::filesystem::directory_entry entry : + std::filesystem::directory_iterator(elevationOutputDir)) { + auto tile = CDBTile::createFromFile(entry.path().filename().string()); + if (tile) { + maxLevel = glm::max(tile->getLevel(), maxLevel); + } + } + + REQUIRE(maxLevel == 1); } // remove the test output @@ -519,112 +520,114 @@ TEST_CASE("Test that elevation conversion uses uniform grid mesh instead of simp std::filesystem::path elevationOutputDir = output / "Tiles" / "N32" / "W118" / "Elevation" / "1_1"; { - // confirm that the elevation at LC09 is empty after decimation - float decimateError = 0.5f; - float thresholdIndices = 0.02f; - std::filesystem::path LC09Path = input / "Tiles" / "N32" / "W118" / "001_Elevation" / "LC" / "U0" - / "N32W118_D001_S001_T001_LC09_U0_R0.tif"; - auto elevation = CDBElevation::createFromFile(LC09Path); - REQUIRE(elevation != std::nullopt); - size_t targetIndices = static_cast( - thresholdIndices * static_cast(elevation->getUniformGridMesh().indices.size())); - auto simplied = elevation->createSimplifiedMesh(targetIndices, decimateError); - REQUIRE(simplied.indices.size() == 0); - REQUIRE(simplied.positionRTCs.size() == 0); - REQUIRE(simplied.normals.size() == 0); - REQUIRE(simplied.UVs.size() == 0); - - // convert the CDB - Converter converter(input, output); - converter.setElevationDecimateError(decimateError); - converter.setElevationThresholdIndices(thresholdIndices); - converter.setElevationLODOnly(true); - converter.convert(); - - // check that LC09 is using uniform grid - std::ifstream fs(elevationOutputDir / "N32W118_D001_S001_T001_LC09_U0_R0.b3dm", std::ios::binary); - B3dmHeader b3dm; - fs.read(reinterpret_cast(&b3dm), sizeof(b3dm)); - - size_t glbBegin = sizeof(b3dm) + b3dm.featureTableJsonByteLength + b3dm.featureTableBinByteLength - + b3dm.batchTableJsonByteLength + b3dm.batchTableBinByteLength; - std::vector glb(b3dm.byteLength - glbBegin); - - fs.seekg(glbBegin); - fs.read(reinterpret_cast(glb.data()), glb.size()); - - tinygltf::TinyGLTF loader; - tinygltf::Model model; - std::string err; - std::string warn; - loader.LoadBinaryFromMemory(&model, - &err, - &warn, - glb.data(), - static_cast(glb.size()), - elevationOutputDir.string()); - - REQUIRE(model.meshes.size() == 1); - - // check mesh primitive - const auto &gltfMesh = model.meshes.front(); - REQUIRE(gltfMesh.primitives.size() == 1); - - const auto &gltfPrimitive = gltfMesh.primitives.front(); - REQUIRE(gltfPrimitive.mode == TINYGLTF_MODE_TRIANGLES); - REQUIRE(gltfPrimitive.indices == 0); - REQUIRE(gltfPrimitive.attributes.at("POSITION") == 1); - REQUIRE(gltfPrimitive.attributes.at("TEXCOORD_0") == 2); - - // check accessors - const auto &uniformElevation = elevation->getUniformGridMesh(); - const auto &indicesAccessor = model.accessors[gltfPrimitive.indices]; - REQUIRE(indicesAccessor.count == uniformElevation.indices.size()); - REQUIRE(indicesAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT); - - const auto &positionAccessor = model.accessors[gltfPrimitive.attributes.at("POSITION")]; - REQUIRE(positionAccessor.count == uniformElevation.positionRTCs.size()); - REQUIRE(positionAccessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT); - REQUIRE(positionAccessor.type == TINYGLTF_TYPE_VEC3); - - const auto &texCoordAccessor = model.accessors[gltfPrimitive.attributes.at("TEXCOORD_0")]; - REQUIRE(texCoordAccessor.count == uniformElevation.UVs.size()); - REQUIRE(texCoordAccessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT); - REQUIRE(texCoordAccessor.type == TINYGLTF_TYPE_VEC2); - - // check buffer view - const auto &gltfBufferData = model.buffers.front().data; - - const auto &indicesBufferView = model.bufferViews[indicesAccessor.bufferView]; - size_t index = 0; - for (size_t i = indicesBufferView.byteOffset; i < indicesBufferView.byteLength; - i += sizeof(unsigned int)) { - unsigned gltfIndex = 0; - std::memcpy(&gltfIndex, &gltfBufferData[i], sizeof(unsigned int)); - REQUIRE(gltfIndex == uniformElevation.indices[index]); - ++index; - } - - const auto &positionBufferView = model.bufferViews[positionAccessor.bufferView]; - index = 0; - for (size_t i = positionBufferView.byteOffset; i < positionBufferView.byteLength; i += sizeof(glm::vec3)) { - glm::vec3 gltfPosition; - std::memcpy(&gltfPosition, &gltfBufferData[i], sizeof(glm::vec3)); - REQUIRE(gltfPosition.x == Approx(uniformElevation.positionRTCs[index].x)); - REQUIRE(gltfPosition.y == Approx(uniformElevation.positionRTCs[index].y)); - REQUIRE(gltfPosition.z == Approx(uniformElevation.positionRTCs[index].z)); - ++index; - } - - const auto &texCoordBufferView = model.bufferViews[texCoordAccessor.bufferView]; - index = 0; - for (size_t i = texCoordBufferView.byteOffset; i < texCoordBufferView.byteLength; i += sizeof(glm::vec2)) { - glm::vec2 gltfTexCoord; - std::memcpy(&gltfTexCoord, &gltfBufferData[i], sizeof(glm::vec2)); - REQUIRE(gltfTexCoord.x == Approx(uniformElevation.UVs[index].x)); - REQUIRE(gltfTexCoord.y == Approx(uniformElevation.UVs[index].y)); - ++index; - } + // confirm that the elevation at LC09 is empty after decimation + float decimateError = 0.5f; + float thresholdIndices = 0.02f; + std::filesystem::path LC09Path = input / "Tiles" / "N32" / "W118" / "001_Elevation" / "LC" / "U0" + / "N32W118_D001_S001_T001_LC09_U0_R0.tif"; + auto elevation = CDBElevation::createFromFile(LC09Path); + REQUIRE(elevation != std::nullopt); + size_t targetIndices = static_cast( + thresholdIndices * static_cast(elevation->getUniformGridMesh().indices.size())); + auto simplied = elevation->createSimplifiedMesh(targetIndices, decimateError); + REQUIRE(simplied.indices.size() == 0); + REQUIRE(simplied.positionRTCs.size() == 0); + REQUIRE(simplied.normals.size() == 0); + REQUIRE(simplied.UVs.size() == 0); + + // convert the CDB + Converter converter(input, output); + converter.setElevationDecimateError(decimateError); + converter.setElevationThresholdIndices(thresholdIndices); + converter.setElevationLODOnly(true); + converter.convert(); + + // check that LC09 is using uniform grid + std::ifstream fs(elevationOutputDir / "N32W118_D001_S001_T001_LC09_U0_R0.b3dm", std::ios::binary); + B3dmHeader b3dm; + fs.read(reinterpret_cast(&b3dm), sizeof(b3dm)); + + size_t glbBegin = sizeof(b3dm) + b3dm.featureTableJsonByteLength + b3dm.featureTableBinByteLength + + b3dm.batchTableJsonByteLength + b3dm.batchTableBinByteLength; + std::vector glb(b3dm.byteLength - glbBegin); + + fs.seekg(glbBegin); + fs.read(reinterpret_cast(glb.data()), glb.size()); + + tinygltf::TinyGLTF loader; + tinygltf::Model model; + std::string err; + std::string warn; + loader.LoadBinaryFromMemory(&model, + &err, + &warn, + glb.data(), + static_cast(glb.size()), + elevationOutputDir.string()); + + REQUIRE(model.meshes.size() == 1); + + // check mesh primitive + const auto &gltfMesh = model.meshes.front(); + REQUIRE(gltfMesh.primitives.size() == 1); + + const auto &gltfPrimitive = gltfMesh.primitives.front(); + REQUIRE(gltfPrimitive.mode == TINYGLTF_MODE_TRIANGLES); + REQUIRE(gltfPrimitive.indices == 0); + REQUIRE(gltfPrimitive.attributes.at("POSITION") == 1); + REQUIRE(gltfPrimitive.attributes.at("TEXCOORD_0") == 2); + + // check accessors + const auto &uniformElevation = elevation->getUniformGridMesh(); + const auto &indicesAccessor = model.accessors[gltfPrimitive.indices]; + REQUIRE(indicesAccessor.count == uniformElevation.indices.size()); + REQUIRE(indicesAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT); + + const auto &positionAccessor = model.accessors[gltfPrimitive.attributes.at("POSITION")]; + REQUIRE(positionAccessor.count == uniformElevation.positionRTCs.size()); + REQUIRE(positionAccessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT); + REQUIRE(positionAccessor.type == TINYGLTF_TYPE_VEC3); + + const auto &texCoordAccessor = model.accessors[gltfPrimitive.attributes.at("TEXCOORD_0")]; + REQUIRE(texCoordAccessor.count == uniformElevation.UVs.size()); + REQUIRE(texCoordAccessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT); + REQUIRE(texCoordAccessor.type == TINYGLTF_TYPE_VEC2); + + // check buffer view + const auto &gltfBufferData = model.buffers.front().data; + + const auto &indicesBufferView = model.bufferViews[indicesAccessor.bufferView]; + size_t index = 0; + for (size_t i = indicesBufferView.byteOffset; i < indicesBufferView.byteLength; + i += sizeof(unsigned int)) { + unsigned gltfIndex = 0; + std::memcpy(&gltfIndex, &gltfBufferData[i], sizeof(unsigned int)); + REQUIRE(gltfIndex == uniformElevation.indices[index]); + ++index; + } + + const auto &positionBufferView = model.bufferViews[positionAccessor.bufferView]; + index = 0; + for (size_t i = positionBufferView.byteOffset; i < positionBufferView.byteLength; + i += sizeof(glm::vec3)) { + glm::vec3 gltfPosition; + std::memcpy(&gltfPosition, &gltfBufferData[i], sizeof(glm::vec3)); + REQUIRE(gltfPosition.x == Approx(uniformElevation.positionRTCs[index].x)); + REQUIRE(gltfPosition.y == Approx(uniformElevation.positionRTCs[index].y)); + REQUIRE(gltfPosition.z == Approx(uniformElevation.positionRTCs[index].z)); + ++index; + } + + const auto &texCoordBufferView = model.bufferViews[texCoordAccessor.bufferView]; + index = 0; + for (size_t i = texCoordBufferView.byteOffset; i < texCoordBufferView.byteLength; + i += sizeof(glm::vec2)) { + glm::vec2 gltfTexCoord; + std::memcpy(&gltfTexCoord, &gltfBufferData[i], sizeof(glm::vec2)); + REQUIRE(gltfTexCoord.x == Approx(uniformElevation.UVs[index].x)); + REQUIRE(gltfTexCoord.y == Approx(uniformElevation.UVs[index].y)); + ++index; + } } // remove the test output diff --git a/Tests/CDBGSModelsTest.cpp b/Tests/CDBGSModelsTest.cpp index a897be7..24fa5f0 100644 --- a/Tests/CDBGSModelsTest.cpp +++ b/Tests/CDBGSModelsTest.cpp @@ -172,33 +172,35 @@ TEST_CASE("Test converting GSModel to tileset.json", "[CDBGSModels]") std::filesystem::path output = "GSModelsWithGTModelTexture"; { - Converter converter(CDBPath, output); - converter.convert(); - - // make sure every zip file in GSModelGeometry will have a corresponding b3dm in the output - size_t geometryModelCount = 0; - std::filesystem::path GSModelGeometryInput = CDBPath / "Tiles" / "N32" / "W118" / "300_GSModelGeometry"; - std::filesystem::path tilesetPath = output / "Tiles" / "N32" / "W118" / "GSModels" / "1_1"; - for (std::filesystem::directory_entry levelDir : - std::filesystem::directory_iterator(GSModelGeometryInput)) { - for (std::filesystem::directory_entry UREFDir : std::filesystem::directory_iterator(levelDir)) { - for (std::filesystem::directory_entry tilePath : std::filesystem::directory_iterator(UREFDir)) { - auto GSModelGeometryTile = CDBTile::createFromFile(tilePath.path().stem().string()); - REQUIRE(std::filesystem::exists( - tilesetPath / (GSModelGeometryTile->getRelativePath().stem().string() + ".b3dm"))); - ++geometryModelCount; - } - } - } - - REQUIRE(geometryModelCount == 3); - - // check the tileset - std::ifstream verifiedJS(CDBPath / "VerifiedTileset.json"); - std::ifstream testJS(tilesetPath / "tileset.json"); - nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); - nlohmann::json testJson = nlohmann::json::parse(testJS); - REQUIRE(testJson == verifiedJson); + Converter converter(CDBPath, output); + converter.convert(); + + // make sure every zip file in GSModelGeometry will have a corresponding b3dm in the output + size_t geometryModelCount = 0; + std::filesystem::path GSModelGeometryInput = CDBPath / "Tiles" / "N32" / "W118" + / "300_GSModelGeometry"; + std::filesystem::path tilesetPath = output / "Tiles" / "N32" / "W118" / "GSModels" / "1_1"; + for (std::filesystem::directory_entry levelDir : + std::filesystem::directory_iterator(GSModelGeometryInput)) { + for (std::filesystem::directory_entry UREFDir : std::filesystem::directory_iterator(levelDir)) { + for (std::filesystem::directory_entry tilePath : + std::filesystem::directory_iterator(UREFDir)) { + auto GSModelGeometryTile = CDBTile::createFromFile(tilePath.path().stem().string()); + REQUIRE(std::filesystem::exists( + tilesetPath / (GSModelGeometryTile->getRelativePath().stem().string() + ".b3dm"))); + ++geometryModelCount; + } + } + } + + REQUIRE(geometryModelCount == 3); + + // check the tileset + std::ifstream verifiedJS(CDBPath / "VerifiedTileset.json"); + std::ifstream testJS(tilesetPath / "tileset.json"); + nlohmann::json verifiedJson = nlohmann::json::parse(verifiedJS); + nlohmann::json testJson = nlohmann::json::parse(testJS); + REQUIRE(testJson == verifiedJson); } // remove the test output diff --git a/Tests/CDBGeometryVectorsTest.cpp b/Tests/CDBGeometryVectorsTest.cpp index 88b35ce..c85d367 100644 --- a/Tests/CDBGeometryVectorsTest.cpp +++ b/Tests/CDBGeometryVectorsTest.cpp @@ -157,4 +157,3 @@ TEST_CASE("Test create CDBGeometryVector", "[CDBGeometryVectors]") REQUIRE(vector == std::nullopt); } } - From 95745e7492c5ddc53b5e5134c6ce83f9da5f693d Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 7 Dec 2020 16:45:44 -0500 Subject: [PATCH 21/21] Update CHANGES.md --- CHANGES.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 89d14b9..6c0957f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,13 +1,13 @@ Change Log ========== -### 0.0.1 - 2020-??-?? +### 0.1.0 - 2020-??-?? * Fixed a bug where GS model attributes and positions couldn't be processed due to too many files being open. [#23](https://github.com/CesiumGS/cdb-to-3dtiles/pull/23) -* Provide `--combine` option to combine multiple tilesets into one. [#19](https://github.com/CesiumGS/cdb-to-3dtiles/issues/19) -* Fixed a bug where empty simplified terrain mesh is exported to gltf. [#25](https://github.com/CesiumGS/cdb-to-3dtiles/pull/25) -* Support window build. [#17](https://github.com/CesiumGS/cdb-to-3dtiles/issues/17) +* Fixed a bug where empty simplified terrain mesh is exported to glTF. [#25](https://github.com/CesiumGS/cdb-to-3dtiles/pull/25) * Fixed a bug where leaf tiles were being given non-zero geometric errors. [#36](https://github.com/CesiumGS/cdb-to-3dtiles/pull/36) +* Added support for building with Visual Studio on Windows. [#17](https://github.com/CesiumGS/cdb-to-3dtiles/issues/17) +* Added `--combine` option to combine multiple tilesets into one. [#19](https://github.com/CesiumGS/cdb-to-3dtiles/issues/19) ### 0.0.0 - 2020-11-16