diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5ece70b8..95f856ed 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,10 +35,6 @@ alp_add_git_repository(stb URL https://github.com/nothings/stb COMMITISH f4a71b1 add_library(stb INTERFACE) target_include_directories(stb SYSTEM INTERFACE ${stb_SOURCE_DIR}) -alp_add_git_repository(tl_expected URL https://github.com/TartanLlama/expected.git COMMITISH v1.1.0 DO_NOT_ADD_SUBPROJECT) -add_library(tl_expected INTERFACE) -target_include_directories(tl_expected SYSTEM INTERFACE ${tl_expected_SOURCE_DIR}/include) - set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "SPDLOG_FMT_EXTERNAL" FORCE) alp_add_git_repository(spdlog URL https://github.com/gabime/spdlog.git COMMITISH v1.17.0) @@ -54,7 +50,7 @@ set(LIBIGL_COPYLEFT_CORE ON) set(LIBIGL_RESTRICTED_TRIANGLE ON) alp_add_git_repository(libigl URL https://github.com/libigl/libigl.git COMMITISH v2.6.0) -alp_add_git_repository(radix URL https://github.com/AlpineMapsOrg/radix.git COMMITISH 2ce3484513b826bb11e5433f868b048eaffe3e5d NOT_SYSTEM DEEP_CLONE) +alp_add_git_repository(radix URL https://github.com/AlpineMapsOrg/radix.git COMMITISH 9720bce6399f4581e35edc88f87651f020348b34 NOT_SYSTEM DEEP_CLONE) set(ALP_CLI_COMPONENT_OPTIONS ALP_BUILD_SF_MERGER diff --git a/src/dag_builder/encoded.h b/src/dag_builder/encoded.h index 769e567f..2ccee0e9 100644 --- a/src/dag_builder/encoded.h +++ b/src/dag_builder/encoded.h @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include #include @@ -165,14 +165,14 @@ auto serialize(Archive &archive, Clustering &clustering) { } } -inline tl::expected +inline std::expected save_clustering(const Clustering &clustering, const std::filesystem::path &path, const bool make_dirs = true) { return ::io::write_to_path(clustering, path, make_dirs); } -inline tl::expected +inline std::expected load_clustering(const std::filesystem::path &path) { return ::io::read_from_path(path); } diff --git a/src/dag_builder/thread_safe_storage.h b/src/dag_builder/thread_safe_storage.h index 4190acac..b0d25998 100644 --- a/src/dag_builder/thread_safe_storage.h +++ b/src/dag_builder/thread_safe_storage.h @@ -4,7 +4,7 @@ #include #include -#include +#include #include "octree/Id.h" @@ -33,7 +33,7 @@ class ThreadSafeStorage { return std::move(this->_storage); } - tl::expected load(const octree::Id &id) const { + std::expected load(const octree::Id &id) const { std::shared_lock lock(this->_mutex); return this->_storage.load(id); } @@ -47,7 +47,7 @@ class ThreadSafeStorage { return this->_storage.base_path(); } - tl::expected save(const octree::Id &id, const value_type &value) const { + std::expected save(const octree::Id &id, const value_type &value) const { std::unique_lock lock(this->_mutex); return this->_storage.save(id, value); } diff --git a/src/mesh_convert/main.cpp b/src/mesh_convert/main.cpp index ef258319..a396ce50 100644 --- a/src/mesh_convert/main.cpp +++ b/src/mesh_convert/main.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include "mesh/SimpleMesh.h" #include "mesh/io.h" @@ -11,7 +11,7 @@ void run(const cli::Args& args) { LOG_INFO("Loading input mesh..."); - const tl::expected load_result = mesh::io::load_from_path(args.input_path); + const std::expected load_result = mesh::io::load_from_path(args.input_path); if (!load_result.has_value()) { LOG_ERROR("Failed to load mesh: {}", load_result.error().description()); return; @@ -30,7 +30,7 @@ void run(const cli::Args& args) { } LOG_INFO("Writing output mesh..."); - const tl::expected save_result = mesh::io::save_to_path(mesh, args.output_path); + const std::expected save_result = mesh::io::save_to_path(mesh, args.output_path); if (!save_result.has_value()) { LOG_ERROR("Failed to save mesh: {}", save_result.error().description()); return; diff --git a/src/sf_builder/CMakeLists.txt b/src/sf_builder/CMakeLists.txt index c50cae1c..cfdabb70 100644 --- a/src/sf_builder/CMakeLists.txt +++ b/src/sf_builder/CMakeLists.txt @@ -3,7 +3,7 @@ add_library(sfbuilderlib mesh_builder.cpp ) target_include_directories(sfbuilderlib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(sfbuilderlib PUBLIC terrainlib spdlog tl_expected) +target_link_libraries(sfbuilderlib PUBLIC terrainlib spdlog) add_executable(sf-builder main.cpp) target_link_libraries(sf-builder PRIVATE sfbuilderlib CLI11::CLI11) diff --git a/src/sf_builder/mesh_builder.cpp b/src/sf_builder/mesh_builder.cpp index 364d2368..cba54e72 100644 --- a/src/sf_builder/mesh_builder.cpp +++ b/src/sf_builder/mesh_builder.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -15,7 +16,7 @@ #include "mesh/SimpleMesh.h" #include "mesh/cleanup.h" #include "mesh_builder.h" -#include "raster.h" +#include #include "raw_dataset_reader.h" #include "srs.h" #include "mesh/clip.h" @@ -50,7 +51,7 @@ glm::dvec2 apply_transform(std::array transform, const glm::tvec2 return result; } -glm::dvec3 convert_pixel_to_vertex(const float height, const raster::Coords pixel_coords, const RawDatasetReader& reader, const PixelBounds& pixel_bounds) { +glm::dvec3 convert_pixel_to_vertex(const float height, const glm::uvec2 pixel_coords, const RawDatasetReader& reader, const PixelBounds& pixel_bounds) { const glm::dvec2 point_offset_in_raster(0.5); // Convert pixel coordinates into a point in the dataset's srs. const glm::dvec2 coords_raster_relative = glm::dvec2(pixel_coords) + point_offset_in_raster; const glm::dvec2 coords_raster_absolute = coords_raster_relative + glm::dvec2(pixel_bounds.min); @@ -58,9 +59,9 @@ glm::dvec3 convert_pixel_to_vertex(const float height, const raster::Coords pixe return coords_source; } -SimpleMesh meshify(const raster::Raster& source_points, const raster::Mask& mask) { +SimpleMesh meshify(const radix::Raster& source_points, const radix::RasterMask& mask) { // Compact the vertex grid into a list of valid ones. - const size_t valid_vertex_count = std::reduce(mask.begin(), mask.end(), 0); + const size_t valid_vertex_count = std::reduce(mask.begin(), mask.end(), size_t(0)); // Check if we even have any valid vertices. Can happen if all of the region is padding. if (valid_vertex_count == 0) { return SimpleMesh(); @@ -69,11 +70,15 @@ SimpleMesh meshify(const raster::Raster& source_points, const raster std::vector positions; positions.reserve(valid_vertex_count); - const raster::Raster vertex_index_map = raster::transform(source_points, mask, [&](const glm::dvec3 &point) -> size_t { + auto vertex_index_map_result = radix::raster::transform(source_points, mask, [&](const glm::dvec3& point) -> size_t { const size_t index = positions.size(); positions.push_back(point); return index; }); + DEBUG_ASSERT(vertex_index_map_result.has_value()); + if (!vertex_index_map_result.has_value()) + return {}; + const auto vertex_index_map = std::move(*vertex_index_map_result); DEBUG_ASSERT(positions.size() == valid_vertex_count); // Allocate triangle vector @@ -81,13 +86,13 @@ SimpleMesh meshify(const raster::Raster& source_points, const raster std::vector triangles; triangles.reserve(max_triangle_count); - for (size_t y = 0; y < source_points.height() - 1; y++) { - for (size_t x = 0; x < source_points.width() - 1; x++) { - const std::array quad { - raster::Coords{x, y}, - raster::Coords{x + 1, y}, - raster::Coords{x + 1, y + 1}, - raster::Coords{x, y + 1}}; + for (unsigned y = 0; y < source_points.height() - 1; y++) { + for (unsigned x = 0; x < source_points.width() - 1; x++) { + const std::array quad { + glm::uvec2{x, y}, + glm::uvec2{x + 1, y}, + glm::uvec2{x + 1, y + 1}, + glm::uvec2{x, y + 1}}; for (uint32_t i = 0; i < 4; i++) { const auto& v0 = quad[i]; @@ -142,7 +147,7 @@ radix::geometry::Aabb3d extend_bounds_to_3d(radix::geometry::Aabb2d bounds2d) { } } -tl::expected build_reference_mesh_tile( +std::expected build_reference_mesh_tile( Dataset &dataset, const OGRSpatialReference &mesh_srs, const OGRSpatialReference &tile_srs, const radix::tile::SrsBounds &tile_bounds, @@ -150,7 +155,7 @@ tl::expected build_reference_mesh_tile( return build_reference_mesh_patch(dataset, mesh_srs, tile_srs, extend_bounds_to_3d(tile_bounds), texture_srs, texture_bounds); } -tl::expected build_reference_mesh_patch( +std::expected build_reference_mesh_patch( Dataset &dataset, const OGRSpatialReference &mesh_srs, const OGRSpatialReference &clip_srs, const radix::geometry::Aabb3d &clip_bounds, @@ -171,35 +176,39 @@ tl::expected build_reference_mesh_patch( radix::geometry::Aabb2i pixel_bounds = reader.transform_srs_bounds_to_pixel_bounds(target_bounds_in_source_srs); add_border_to_aabb(pixel_bounds, Border(1)); LOG_TRACE("Reading pixels [({}, {})-({}, {})] from dataset", pixel_bounds.min.x, pixel_bounds.min.y, pixel_bounds.max.x, pixel_bounds.max.y); - const std::optional read_result = reader.read_data_in_pixel_bounds_clamped(pixel_bounds); - if (!read_result.has_value() || read_result->size() == 0) { - return tl::unexpected(BuildMeshError::OutOfBounds); + auto read_result = reader.read_data_in_pixel_bounds_clamped(pixel_bounds); + if (!read_result.has_value() || read_result->buffer().empty()) { + return std::unexpected(BuildMeshError::OutOfBounds); } - const raster::HeightMap height_map = read_result.value(); + const radix::Raster height_map = std::move(*read_result); LOG_TRACE("Finding valid pixels"); const float no_data_value = reader.get_no_data_value(); - const raster::Mask valid_mask = raster::transform(height_map, [=](const float height) { + const radix::RasterMask valid_mask = radix::raster::transform(height_map, [=](const float height) { return height != no_data_value; }); LOG_TRACE("Transforming pixels to vertices"); - const raster::Raster source_points = raster::transform(height_map, valid_mask, [&](const float height, const raster::Coords& coords) { + auto source_points_result = radix::raster::transform(height_map, valid_mask, [&](const float height, const glm::uvec2& coords) { return convert_pixel_to_vertex(height, coords, reader, pixel_bounds); }); + DEBUG_ASSERT(source_points_result.has_value()); + if (!source_points_result.has_value()) + return std::unexpected(BuildMeshError::EmptyRegion); + const auto source_points = std::move(*source_points_result); LOG_TRACE("Generating triangles"); SimpleMesh mesh_in_source_srs = meshify(source_points, valid_mask); // Check if we even have any valid vertices. Can happen if all of the region is padding. if (mesh_in_source_srs.vertex_count() == 0 || mesh_in_source_srs.face_count() == 0) { - return tl::unexpected(BuildMeshError::EmptyRegion); + return std::unexpected(BuildMeshError::EmptyRegion); } // Fast check if all vertices will be clipped const radix::geometry::Aabb3d actual_source_bounds = calculate_bounds(mesh_in_source_srs); const radix::geometry::Aabb3d approx_clip_bounds = srs::encompassing_bounds_transfer(source_srs, clip_srs, actual_source_bounds); if (!radix::geometry::intersect(approx_clip_bounds, clip_bounds)) { - return tl::unexpected(BuildMeshError::EmptyRegion); + return std::unexpected(BuildMeshError::EmptyRegion); } LOG_TRACE("Clipping mesh based on target bounds"); @@ -207,7 +216,7 @@ tl::expected build_reference_mesh_patch( SimpleMesh clipped_mesh = mesh::clip_on_bounds(mesh_in_clip_srs, clip_bounds); // Check if there are any vertices left if (clipped_mesh.vertex_count() == 0 || clipped_mesh.face_count() == 0) { - return tl::unexpected(BuildMeshError::EmptyRegion); + return std::unexpected(BuildMeshError::EmptyRegion); } // TODO: move this to another function? diff --git a/src/sf_builder/mesh_builder.h b/src/sf_builder/mesh_builder.h index acf3e4b8..2979042d 100644 --- a/src/sf_builder/mesh_builder.h +++ b/src/sf_builder/mesh_builder.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include "Dataset.h" #include "srs.h" @@ -17,7 +17,7 @@ enum class BuildMeshError { std::ostream &operator<<(std::ostream &os, BuildMeshError error); /// Builds a mesh from the given height dataset. -tl::expected build_reference_mesh_patch( +std::expected build_reference_mesh_patch( Dataset &dataset, const OGRSpatialReference &mesh_srs, const OGRSpatialReference &clip_srs, const radix::geometry::Aabb3d &clip_bounds, diff --git a/src/sf_builder/raster.h b/src/sf_builder/raster.h deleted file mode 100644 index fe9ee5a6..00000000 --- a/src/sf_builder/raster.h +++ /dev/null @@ -1,145 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace raster { - -using Index = size_t; -using Coords = glm::vec<2, size_t>; - -template -class Raster { -public: - Raster() = default; - Raster(Index width, Index height) - : _width(width), _height(height), _data(width * height) { - } - - [[nodiscard]] Index width() const { - return this->_width; - } - [[nodiscard]] Index height() const { - return this->_height; - } - [[nodiscard]] decltype(auto) pixel(const Coords &coords) { - DEBUG_ASSERT(coords.x < this->_width); - DEBUG_ASSERT(coords.y < this->_height); - const Index pixel_index = this->index(coords); - if constexpr (std::is_same_v) { - return static_cast(this->_data[pixel_index]); - } else { - return static_cast(this->_data[pixel_index]); - } - } - [[nodiscard]] decltype(auto) pixel(const Coords &coords) const { - DEBUG_ASSERT(coords.x < this->_width); - DEBUG_ASSERT(coords.y < this->_height); - const Index pixel_index = this->index(coords); - if constexpr (std::is_same_v) { - return static_cast(this->_data[pixel_index]); - } else { - return static_cast(this->_data[pixel_index]); - } - } - - [[nodiscard]] Index index(const Coords& coords) const { - return coords.y * this->_width + coords.x; - } - - [[nodiscard]] T *data() { - return this->_data.data(); - } - [[nodiscard]] const T *data() const { - return this->_data.data(); - } - - [[nodiscard]] Index size() const { - return this->_data.size(); - } - [[nodiscard]] auto begin() { - return this->_data.begin(); - } - [[nodiscard]] auto end() { - return this->_data.end(); - } - [[nodiscard]] auto begin() const { - return this->_data.begin(); - } - [[nodiscard]] auto end() const { - return this->_data.end(); - } - -private: - unsigned _width = 0; - unsigned _height = 0; - std::vector _data; -}; - -using Mask = Raster; -using HeightMap = Raster; - -template -concept TransformFn = requires(F f, In in) { - { f(in) }; -}; - -template -concept TransformFnWithCoords = requires(F f, In in, Coords coord) { - { f(in, coord) }; -}; - -template F> -[[nodiscard]] auto transform(const Raster &input, F &&f) { - using Out = decltype(f(input.pixel(Coords(0, 0)))); - Raster output(input.width(), input.height()); - std::transform(input.begin(), input.end(), output.begin(), std::forward(f)); - return output; -} - -template F> -[[nodiscard]] auto transform(const Raster &input, F &&f) { - using Out = decltype(f(input.pixel(Coords(0, 0)), Coords(0, 0))); - Raster output(input.width(), input.height()); - for (Index y = 0; y < input.height(); ++y) { - for (Index x = 0; x < input.width(); ++x) { - Coords coords(x, y); - output.pixel(coords) = f(input.pixel(coords), coords); - } - } - return output; -} - -template F> -[[nodiscard]] auto transform(const Raster &input, const Mask &mask, F &&f) { - using Out = decltype(f(input.pixel(Coords(0, 0)))); - Raster output(input.width(), input.height()); - for (Index y = 0; y < input.height(); ++y) { - for (Index x = 0; x < input.width(); ++x) { - Coords coords(x, y); - if (mask.pixel(coords)) { - output.pixel(coords) = f(input.pixel(coords)); - } - } - } - return output; -} - -template F> -[[nodiscard]] auto transform(const Raster &input, const Mask &mask, F &&f) { - using Out = decltype(f(input.pixel(Coords(0, 0)), Coords(0, 0))); - Raster output(input.width(), input.height()); - for (Index y = 0; y < input.height(); ++y) { - for (Index x = 0; x < input.width(); ++x) { - Coords coords(x, y); - if (mask.pixel(coords)) { - output.pixel(coords) = f(input.pixel(coords), coords); - } - } - } - return output; -} -} diff --git a/src/sf_builder/raw_dataset_reader.h b/src/sf_builder/raw_dataset_reader.h index 9eed966e..88e4d9cb 100644 --- a/src/sf_builder/raw_dataset_reader.h +++ b/src/sf_builder/raw_dataset_reader.h @@ -7,7 +7,7 @@ #include #include "log.h" -#include "raster.h" +#include namespace terrainbuilder { @@ -46,7 +46,7 @@ class RawDatasetReader { } // TODO: support reading other data types - std::optional read_data_in_pixel_bounds(const radix::geometry::Aabb2i& bounds) { + std::optional> read_data_in_pixel_bounds(const radix::geometry::Aabb2i& bounds) { DEBUG_ASSERT(glm::all(glm::greaterThanEqual(bounds.min, glm::ivec2(0)))); DEBUG_ASSERT(glm::all(glm::greaterThanEqual(bounds.max, glm::ivec2(0)))); DEBUG_ASSERT(glm::all(glm::lessThan(bounds.min, glm::ivec2(this->dataset_size())))); @@ -56,7 +56,7 @@ class RawDatasetReader { GDALRasterBand *height_band = this->dataset->GetRasterBand(1); // non-owning pointer // Initialize the HeightData for reading - raster::HeightMap height_data(bounds.width(), bounds.height()); + radix::Raster height_data(glm::uvec2(bounds.width(), bounds.height())); if (bounds.width() == 0 || bounds.height() == 0) { LOG_WARN("Target dataset bounds are empty"); return height_data; @@ -65,7 +65,7 @@ class RawDatasetReader { // Read data from the heights band into heights_data const int read_result = height_band->RasterIO( GF_Read, bounds.min.x, bounds.min.y, bounds.width(), bounds.height(), - static_cast(height_data.data()), bounds.width(), bounds.height(), GDT_Float32, 0, 0); + static_cast(height_data.buffer().data()), bounds.width(), bounds.height(), GDT_Float32, 0, 0); if (read_result != CE_None) { const char * message = CPLGetLastErrorMsg(); @@ -75,7 +75,7 @@ class RawDatasetReader { return height_data; } - std::optional read_data_in_pixel_bounds_clamped(radix::geometry::Aabb2i &bounds) { + std::optional> read_data_in_pixel_bounds_clamped(radix::geometry::Aabb2i &bounds) { const auto original_bounds = bounds; const glm::ivec2 max_in_bounds = glm::ivec2(this->dataset_size()) - glm::ivec2(1); @@ -90,13 +90,13 @@ class RawDatasetReader { if (bounds.width() == 0 || bounds.height() == 0) { LOG_WARN("Target dataset bounds are empty (clamped)"); - return raster::HeightMap(0, 0); + return radix::Raster(); } return this->read_data_in_pixel_bounds(bounds); } - std::optional read_data_in_srs_bounds(const radix::tile::SrsBounds &bounds) { + std::optional> read_data_in_srs_bounds(const radix::tile::SrsBounds &bounds) { // Transform the SrsBounds to pixel space radix::geometry::Aabb2i pixel_bounds = this->transform_srs_bounds_to_pixel_bounds(bounds); diff --git a/src/sf_builder/terrainbuilder.cpp b/src/sf_builder/terrainbuilder.cpp index ca5a74db..09d47079 100644 --- a/src/sf_builder/terrainbuilder.cpp +++ b/src/sf_builder/terrainbuilder.cpp @@ -56,7 +56,7 @@ std::optional build_patch( std::chrono::high_resolution_clock::time_point start; start = std::chrono::high_resolution_clock::now(); LOG_INFO("Building mesh..."); - tl::expected mesh_result = build_reference_mesh_patch( + std::expected mesh_result = build_reference_mesh_patch( dataset, mesh_srs, target_bounds_srs, target_bounds, diff --git a/src/sf_merger/mask.h b/src/sf_merger/mask.h index 6ca6ccd3..47f2b875 100644 --- a/src/sf_merger/mask.h +++ b/src/sf_merger/mask.h @@ -253,7 +253,7 @@ auto length2(const Vec &v) -> decltype(glm::dot(v, v)) { } // namespace -inline tl::expected load_referenced_from_dataset(Dataset& mask_dataset) { +inline std::expected load_referenced_from_dataset(Dataset& mask_dataset) { GDALDataset *dataset = mask_dataset.gdalDataset(); MultipolygonWithHoles2 polygons; @@ -265,7 +265,7 @@ inline tl::expected load_referenced_from_datas if (polygons.is_empty()) { LOG_ERROR("No valid polygons found in mask dataset '{}'", mask_dataset.name()); - return tl::unexpected(LoadErrorKind::EmptySource); + return std::unexpected(LoadErrorKind::EmptySource); } OGRSpatialReference srs; @@ -438,25 +438,25 @@ inline MeshMask extrude( return extrude(mask, padded_radius_range); } -inline tl::expected load_referenced_from_path(const std::filesystem::path &path) { +inline std::expected load_referenced_from_path(const std::filesystem::path &path) { if (!std::filesystem::exists(path)) { LOG_ERROR("Mask file does not exist: {}", path); - return tl::unexpected(LoadErrorKind::FileNotFound); + return std::unexpected(LoadErrorKind::FileNotFound); } auto ds_opt = Dataset::open_vector(path); if (!ds_opt.has_value()) { LOG_ERROR("Failed to load mask datset: {}", path); - return tl::unexpected(LoadErrorKind::FileNotFound); + return std::unexpected(LoadErrorKind::FileNotFound); } Dataset dataset = std::move(ds_opt.value()); return load_referenced_from_dataset(dataset); } -inline tl::expected load_from_path(const std::filesystem::path &path, const glm::dvec2& radius_range) { +inline std::expected load_from_path(const std::filesystem::path &path, const glm::dvec2& radius_range) { auto ref_mask_res = load_referenced_from_path(path); if (!ref_mask_res.has_value()) { - return tl::unexpected(ref_mask_res.error()); + return std::unexpected(ref_mask_res.error()); } ReferencedPolygonMask ref_polygon_mask = std::move(ref_mask_res.value()); SpherePolygonMask sphere_polygon_mask = project_onto_sphere(std::move(ref_polygon_mask), radius_range.x); diff --git a/src/terrainlib/CMakeLists.txt b/src/terrainlib/CMakeLists.txt index 79dd32d6..2e321547 100644 --- a/src/terrainlib/CMakeLists.txt +++ b/src/terrainlib/CMakeLists.txt @@ -71,7 +71,6 @@ target_link_libraries(terrainlib PUBLIC zpp_bits cgltf TBB::tbb - tl_expected opencv_core opencv_imgproc opencv_imgcodecs diff --git a/src/terrainlib/io/bytes.cpp b/src/terrainlib/io/bytes.cpp index 499b0ee9..1412ce33 100644 --- a/src/terrainlib/io/bytes.cpp +++ b/src/terrainlib/io/bytes.cpp @@ -6,7 +6,7 @@ namespace io { -tl::expected write_bytes_to_path(const std::span bytes, const std::filesystem::path &path, bool make_dirs) { +std::expected write_bytes_to_path(const std::span bytes, const std::filesystem::path &path, bool make_dirs) { LOG_TRACE("Writing bytes to path {}", path); if (make_dirs) { @@ -16,31 +16,31 @@ tl::expected write_bytes_to_path(const std::span byt std::ofstream file(path, std::ios::binary); if (!file.is_open()) { LOG_DEBUG("Failed to open file for writing {}", path); - return tl::unexpected(Error::OpenFile); + return std::unexpected(Error::OpenFile); } file.write(reinterpret_cast(bytes.data()), static_cast(bytes.size())); if (!file.good()) { LOG_ERROR("Failed to write bytes to file {}", path); - return tl::unexpected(Error::WriteBytes); + return std::unexpected(Error::WriteBytes); } return {}; } -tl::expected, Error> read_bytes_from_path(const std::filesystem::path& path) { +std::expected, Error> read_bytes_from_path(const std::filesystem::path& path) { LOG_TRACE("Reading bytes from path {}", path); std::ifstream file(path, std::ios::binary | std::ios::ate); if (!file.is_open()) { LOG_DEBUG("Failed to open file for reading {}", path); - return tl::unexpected(Error::OpenFile); + return std::unexpected(Error::OpenFile); } const std::streamsize size = file.tellg(); if (size < 0) { LOG_ERROR("Failed to determine size for file {}", path); - return tl::unexpected(Error::DetermineSize); + return std::unexpected(Error::DetermineSize); } std::vector buffer(static_cast(size)); @@ -49,7 +49,7 @@ tl::expected, Error> read_bytes_from_path(const std::filesy if (!file.good()) { LOG_ERROR("Failed to read bytes from file {}", path); - return tl::unexpected(Error::ReadBytes); + return std::unexpected(Error::ReadBytes); } return buffer; diff --git a/src/terrainlib/io/bytes.h b/src/terrainlib/io/bytes.h index 5c156cf4..be3852dc 100644 --- a/src/terrainlib/io/bytes.h +++ b/src/terrainlib/io/bytes.h @@ -4,13 +4,13 @@ #include #include -#include +#include #include "io/Error.h" namespace io { -tl::expected write_bytes_to_path(const std::span bytes, const std::filesystem::path &path, bool make_dirs = true); -tl::expected, Error> read_bytes_from_path(const std::filesystem::path &path); +std::expected write_bytes_to_path(const std::span bytes, const std::filesystem::path &path, bool make_dirs = true); +std::expected, Error> read_bytes_from_path(const std::filesystem::path &path); } diff --git a/src/terrainlib/io/serialize.h b/src/terrainlib/io/serialize.h index 55919e23..ffda2d1f 100644 --- a/src/terrainlib/io/serialize.h +++ b/src/terrainlib/io/serialize.h @@ -4,21 +4,21 @@ #include #include -#include +#include #include "io/Error.h" namespace io { template -tl::expected, Error> write_to_bytes(const T &value); +std::expected, Error> write_to_bytes(const T &value); template -tl::expected read_from_bytes(const std::span bytes); +std::expected read_from_bytes(const std::span bytes); template -tl::expected write_to_path(const T &value, const std::filesystem::path &path, bool make_dirs = true); +std::expected write_to_path(const T &value, const std::filesystem::path &path, bool make_dirs = true); template -tl::expected read_from_path(const std::filesystem::path &path); +std::expected read_from_path(const std::filesystem::path &path); } diff --git a/src/terrainlib/io/serialize.inl b/src/terrainlib/io/serialize.inl index 90d7080b..80324d03 100644 --- a/src/terrainlib/io/serialize.inl +++ b/src/terrainlib/io/serialize.inl @@ -9,7 +9,7 @@ namespace io { template -tl::expected, Error> write_to_bytes(const T &value) { +std::expected, Error> write_to_bytes(const T &value) { std::vector data; zpp::bits::out out(data); const auto result = out(value); @@ -21,11 +21,11 @@ tl::expected, Error> write_to_bytes(const T &value) { case std::errc::no_buffer_space: // growing buffer would grow beyond the allocation limits or overflow. case std::errc::message_size: // message size is beyond the user defined allocation limits. case std::errc::result_out_of_range: // attempting to write or read from a too short buffer. - return tl::unexpected(Error::OutOfMemory); + return std::unexpected(Error::OutOfMemory); case std::errc::value_too_large: // varint (variable length integer) encoding is beyond the representation limits. case std::errc::bad_message: // attempt to read a variant of unrecognized type. case std::errc::invalid_argument: // attempting to serialize null pointer or a value-less variant. - return tl::unexpected(Error::Serialize); + return std::unexpected(Error::Serialize); case std::errc::protocol_error: // attempt to deserialize an invalid protocol message. case std::errc::not_supported: // attempt to call an RPC that is not listed as supported. UNREACHABLE(); @@ -37,7 +37,7 @@ tl::expected, Error> write_to_bytes(const T &value) { } template -tl::expected read_from_bytes(const std::span bytes) { +std::expected read_from_bytes(const std::span bytes) { zpp::bits::in in(bytes); T value; const auto result = in(value); @@ -49,11 +49,11 @@ tl::expected read_from_bytes(const std::span bytes) { case std::errc::no_buffer_space: // growing buffer would grow beyond the allocation limits or overflow. case std::errc::message_size: // message size is beyond the user defined allocation limits. case std::errc::result_out_of_range: // attempting to write or read from a too short buffer. - return tl::unexpected(Error::OutOfMemory); + return std::unexpected(Error::OutOfMemory); case std::errc::value_too_large: // varint (variable length integer) encoding is beyond the representation limits. case std::errc::bad_message: // attempt to read a variant of unrecognized type. case std::errc::invalid_argument: // attempting to serialize null pointer or a value-less variant. - return tl::unexpected(Error::Deserialize); + return std::unexpected(Error::Deserialize); case std::errc::protocol_error: // attempt to deserialize an invalid protocol message. case std::errc::not_supported: // attempt to call an RPC that is not listed as supported. UNREACHABLE(); @@ -65,20 +65,20 @@ tl::expected read_from_bytes(const std::span bytes) { } template -tl::expected read_from_path(const std::filesystem::path &path) { +std::expected read_from_path(const std::filesystem::path &path) { const auto result = read_bytes_from_path(path); if (!result.has_value()) { - return tl::unexpected(result.error()); + return std::unexpected(result.error()); } const std::vector bytes = result.value(); return read_from_bytes(bytes); } template -tl::expected write_to_path(const T &value, const std::filesystem::path &path, bool make_dirs) { +std::expected write_to_path(const T &value, const std::filesystem::path &path, bool make_dirs) { const auto result = write_to_bytes(value); if (!result.has_value()) { - return tl::unexpected(result.error()); + return std::unexpected(result.error()); } const std::vector bytes = result.value(); diff --git a/src/terrainlib/mesh/encode.h b/src/terrainlib/mesh/encode.h index 302fd016..eca66508 100644 --- a/src/terrainlib/mesh/encode.h +++ b/src/terrainlib/mesh/encode.h @@ -4,7 +4,7 @@ #include #include -#include +#include #include "log.h" #include "mesh/EncodedMesh.h" @@ -81,7 +81,7 @@ inline std::ostream& operator<<(std::ostream& os, const EncodeError& err) { } template -tl::expected encode(const Simple_& mesh, const EncodeOptions options = {}) { +std::expected encode(const Simple_& mesh, const EncodeOptions options = {}) { using Mesh = Simple_; const size_t vertex_count = mesh.vertex_count(); @@ -96,7 +96,7 @@ tl::expected encode(const Simple_& mesh, const position_buf.resize(meshopt_encodeVertexBufferBound(vertex_count, position_size)); const size_t pos_written = meshopt_encodeVertexBuffer(position_buf.data(), position_buf.size(), mesh.positions.data(), vertex_count, position_size); if (pos_written == 0) { - return tl::unexpected(EncodeError::PositionEncode); + return std::unexpected(EncodeError::PositionEncode); } position_buf.resize(pos_written); } @@ -107,7 +107,7 @@ tl::expected encode(const Simple_& mesh, const uv_buf.resize(meshopt_encodeVertexBufferBound(vertex_count, uv_size)); const size_t uv_written = meshopt_encodeVertexBuffer(uv_buf.data(), uv_buf.size(), mesh.uvs.data(), vertex_count, uv_size); if (uv_written == 0) { - return tl::unexpected(EncodeError::UvEncode); + return std::unexpected(EncodeError::UvEncode); } uv_buf.resize(uv_written); } @@ -121,7 +121,7 @@ tl::expected encode(const Simple_& mesh, const reinterpret_cast(mesh.triangles.data()), index_count); if (index_written == 0) { - return tl::unexpected(EncodeError::TriangleEncode); + return std::unexpected(EncodeError::TriangleEncode); } index_buf.resize(index_written); } @@ -133,7 +133,7 @@ tl::expected encode(const Simple_& mesh, const texture_buf = mesh::io::write_texture_to_encoded_buffer(mesh.texture.value(), options.texture_format); } catch (const cv::Exception &e) { LOG_ERROR("Failed while encoding texture {}", e.what()); - return tl::unexpected(EncodeError::TextureEncode); + return std::unexpected(EncodeError::TextureEncode); } } @@ -189,13 +189,13 @@ inline std::ostream &operator<<(std::ostream &os, const DecodeError &err) { template -tl::expected, DecodeError> decode(const Encoded &encoded, const DecodeOptions = {}) { +std::expected, DecodeError> decode(const Encoded &encoded, const DecodeOptions = {}) { const uint32_t expected_component_type = component_type_id(); const Encoded::Header& header = encoded.header; if (header.version != 1 || header.n_dims != n_dims || header.component_type != expected_component_type) { - return tl::unexpected(DecodeError::IncompatibleData); + return std::unexpected(DecodeError::IncompatibleData); } using Mesh = Simple_; @@ -208,7 +208,7 @@ tl::expected, DecodeError> decode(const Encoded &encoded, con mesh.positions.resize(vertex_count); result = meshopt_decodeVertexBuffer(mesh.positions.data(), vertex_count, position_size, encoded.positions.data(), encoded.positions.size()); if (result != 0) { - return tl::unexpected(DecodeError::PositionDecode); + return std::unexpected(DecodeError::PositionDecode); } // Decode uvs @@ -217,7 +217,7 @@ tl::expected, DecodeError> decode(const Encoded &encoded, con mesh.uvs.resize(vertex_count); result = meshopt_decodeVertexBuffer(mesh.uvs.data(), vertex_count, uv_size, encoded.uvs.data(), encoded.uvs.size()); if (result != 0) { - return tl::unexpected(DecodeError::UvDecode); + return std::unexpected(DecodeError::UvDecode); } } @@ -228,7 +228,7 @@ tl::expected, DecodeError> decode(const Encoded &encoded, con mesh.triangles.resize(face_count); result = meshopt_decodeIndexBuffer(mesh.triangles.data(), index_count, index_size, encoded.triangles.data(), encoded.triangles.size()); if (result != 0) { - return tl::unexpected(DecodeError::TriangleDecode); + return std::unexpected(DecodeError::TriangleDecode); } // Decode texture @@ -237,7 +237,7 @@ tl::expected, DecodeError> decode(const Encoded &encoded, con mesh.texture = mesh::io::read_texture_from_encoded_bytes(encoded.texture); } catch (const cv::Exception &e) { LOG_ERROR("Failed while decoding texture {}", e.what()); - return tl::unexpected(DecodeError::TextureDecode); + return std::unexpected(DecodeError::TextureDecode); } } diff --git a/src/terrainlib/mesh/io.cpp b/src/terrainlib/mesh/io.cpp index 6d56c2c1..467e0537 100644 --- a/src/terrainlib/mesh/io.cpp +++ b/src/terrainlib/mesh/io.cpp @@ -6,7 +6,7 @@ namespace mesh::io { -tl::expected load_from_path( +std::expected load_from_path( const std::filesystem::path &path, const LoadOptions& options) { const std::filesystem::path extension = path.extension(); @@ -15,11 +15,11 @@ tl::expected load_from_path( } else if (extension == ".terrain") { return terrain::load_from_path(path, options); } else { - return tl::unexpected(LoadMeshErrorKind::UnsupportedFormat); + return std::unexpected(LoadMeshErrorKind::UnsupportedFormat); } } -tl::expected save_to_path( +std::expected save_to_path( const SimpleMesh &mesh, const std::filesystem::path &path, const SaveOptions &options) { @@ -33,7 +33,7 @@ tl::expected save_to_path( } else if (extension == ".terrain") { return terrain::save_to_path(mesh, path, options); } else { - return tl::unexpected(SaveMeshErrorKind::UnsupportedFormat); + return std::unexpected(SaveMeshErrorKind::UnsupportedFormat); } } diff --git a/src/terrainlib/mesh/io.h b/src/terrainlib/mesh/io.h index 81fb076e..ea474c73 100644 --- a/src/terrainlib/mesh/io.h +++ b/src/terrainlib/mesh/io.h @@ -2,7 +2,7 @@ #include -#include +#include #include "mesh/SimpleMesh.h" #include "mesh/io/options.h" @@ -10,11 +10,11 @@ namespace mesh::io { -tl::expected load_from_path( +std::expected load_from_path( const std::filesystem::path &path, const LoadOptions& options = {}); -tl::expected save_to_path( +std::expected save_to_path( const SimpleMesh &mesh, const std::filesystem::path &path, const SaveOptions& options = {}); diff --git a/src/terrainlib/mesh/io/gltf.cpp b/src/terrainlib/mesh/io/gltf.cpp index bb01bb45..61c0fec1 100644 --- a/src/terrainlib/mesh/io/gltf.cpp +++ b/src/terrainlib/mesh/io/gltf.cpp @@ -212,7 +212,7 @@ std::optional load_texture_from_material(const cgltf_material &material #define GET_OR_INVALID_FORMAT(var, opt) \ do { \ if (!(opt).has_value()) { \ - return tl::unexpected(LoadMeshErrorKind::InvalidFormat); \ + return std::unexpected(LoadMeshErrorKind::InvalidFormat); \ } else { \ var = opt.value(); \ } \ @@ -277,56 +277,56 @@ static std::string image_ext_to_mime(std::string_view extension) { } } -tl::expected load_raw_from_path(const std::filesystem::path &path) { +std::expected load_raw_from_path(const std::filesystem::path &path) { cgltf_options options = {}; cgltf_data *data = NULL; const std::string path_str = path.string(); const char *path_ptr = path_str.c_str(); cgltf_result result = cgltf_parse_file(&options, path_ptr, &data); if (result != cgltf_result::cgltf_result_success) { - return tl::unexpected(result); + return std::unexpected(result); } result = cgltf_load_buffers(&options, data, path_ptr); if (result != cgltf_result::cgltf_result_success) { cgltf_free(data); - return tl::unexpected(result); + return std::unexpected(result); } result = cgltf_validate(data); if (result != cgltf_result_success) { cgltf_free(data); - return tl::unexpected(result); + return std::unexpected(result); } return RawMesh(data, cgltf_free); } -tl::expected load_from_raw(const RawMesh &raw, const LoadOptions& /* options */) { +std::expected load_from_raw(const RawMesh &raw, const LoadOptions& /* options */) { LOG_TRACE("Loading mesh from gltf data"); const cgltf_data &data = *raw; const auto mesh_opt = get_single_element("mesh", data.meshes_count, data.meshes); if (!mesh_opt.has_value()) { - return tl::unexpected(LoadMeshErrorKind::InvalidFormat); + return std::unexpected(LoadMeshErrorKind::InvalidFormat); } const cgltf_mesh &mesh = mesh_opt.value(); const auto mesh_primitive_opt = get_single_element("mesh primitive", mesh.primitives_count, mesh.primitives); if (!mesh_primitive_opt.has_value()) { - return tl::unexpected(LoadMeshErrorKind::InvalidFormat); + return std::unexpected(LoadMeshErrorKind::InvalidFormat); } const cgltf_primitive &mesh_primitive = mesh_primitive_opt.value(); if (mesh_primitive.type != cgltf_primitive_type::cgltf_primitive_type_triangles) { LOG_ERROR("mesh has invalid primitive type"); - return tl::unexpected(LoadMeshErrorKind::InvalidFormat); + return std::unexpected(LoadMeshErrorKind::InvalidFormat); } // indices if (mesh_primitive.indices == nullptr) { LOG_ERROR("mesh primitive has no indices"); - return tl::unexpected(LoadMeshErrorKind::InvalidFormat); + return std::unexpected(LoadMeshErrorKind::InvalidFormat); } cgltf_accessor &index_accessor = *mesh_primitive.indices; std::vector indices; @@ -342,13 +342,13 @@ tl::expected load_from_raw(const RawMesh &raw, const cgltf_attribute *position_attr = find_attribute_with_type(mesh_primitive.attributes, mesh_primitive.attributes_count, cgltf_attribute_type_position); if (position_attr == nullptr) { LOG_ERROR("mesh has no position attribute"); - return tl::unexpected(LoadMeshErrorKind::InvalidFormat); + return std::unexpected(LoadMeshErrorKind::InvalidFormat); } cgltf_accessor &position_accessor = *position_attr->data; if (position_accessor.type != cgltf_type_vec3) { LOG_WARN("mesh positions are not vec3"); - return tl::unexpected(LoadMeshErrorKind::InvalidFormat); + return std::unexpected(LoadMeshErrorKind::InvalidFormat); } std::vector positions; positions.resize(position_accessor.count); @@ -363,7 +363,7 @@ tl::expected load_from_raw(const RawMesh &raw, const cgltf_accessor &uv_accessor = *uv_attr->data; if (uv_accessor.type != cgltf_type_vec2) { LOG_WARN("mesh uvss are not vec2"); - return tl::unexpected(LoadMeshErrorKind::InvalidFormat); + return std::unexpected(LoadMeshErrorKind::InvalidFormat); } uvs.resize(uv_accessor.count); cgltf_accessor_unpack_floats(&uv_accessor, reinterpret_cast(uvs.data()), uvs.size() * 2); @@ -393,7 +393,7 @@ tl::expected load_from_raw(const RawMesh &raw, const } /// Saves the mesh as a .gltf or .glb file at the given path. -tl::expected save_to_path( +std::expected save_to_path( const SimpleMesh &terrain_mesh, const std::filesystem::path &path, const SaveOptions& options) { @@ -731,10 +731,10 @@ tl::expected save_to_path( return {}; } -tl::expected load_from_path(const std::filesystem::path &path, const LoadOptions &options) { - tl::expected raw_mesh = load_raw_from_path(path); +std::expected load_from_path(const std::filesystem::path &path, const LoadOptions &options) { + std::expected raw_mesh = load_raw_from_path(path); if (!raw_mesh) { - return tl::unexpected(map_cgltf_error(raw_mesh.error())); + return std::unexpected(map_cgltf_error(raw_mesh.error())); } return load_from_raw(*raw_mesh, options); } diff --git a/src/terrainlib/mesh/io/gltf.h b/src/terrainlib/mesh/io/gltf.h index 60757382..20333e93 100644 --- a/src/terrainlib/mesh/io/gltf.h +++ b/src/terrainlib/mesh/io/gltf.h @@ -5,7 +5,7 @@ #include #include -#include +#include #include "mesh/SimpleMesh.h" #include "mesh/io/error.h" @@ -15,19 +15,19 @@ namespace mesh::io::gltf { using RawMesh = std::unique_ptr; -tl::expected load_from_path( +std::expected load_from_path( const std::filesystem::path &path, const LoadOptions &options = {}); -tl::expected load_from_raw( +std::expected load_from_raw( const RawMesh &mesh, const LoadOptions &options = {}); -tl::expected save_to_path( +std::expected save_to_path( const SimpleMesh &mesh, const std::filesystem::path &path, const SaveOptions &options = {}); -// tl::expected save_to_raw(const RawMesh &mesh, const SaveOptions &options = {}); +// std::expected save_to_raw(const RawMesh &mesh, const SaveOptions &options = {}); -tl::expected load_raw_from_path(const std::filesystem::path &path); +std::expected load_raw_from_path(const std::filesystem::path &path); } // namespace mesh::io::gltf diff --git a/src/terrainlib/mesh/io/terrain.cpp b/src/terrainlib/mesh/io/terrain.cpp index 1068a991..d443d6f8 100644 --- a/src/terrainlib/mesh/io/terrain.cpp +++ b/src/terrainlib/mesh/io/terrain.cpp @@ -34,24 +34,24 @@ SaveMeshError save_error_from_io_error(::io::Error error) { } } -tl::expected write_bytes_to_path( +std::expected write_bytes_to_path( const std::span bytes, const std::filesystem::path &path) { const auto result = ::io::write_bytes_to_path(bytes, path); if (!result.has_value()) { - return tl::unexpected(save_error_from_io_error(result.error())); + return std::unexpected(save_error_from_io_error(result.error())); } return {}; } -tl::expected, LoadMeshError> read_bytes_from_path(const std::filesystem::path &path) { +std::expected, LoadMeshError> read_bytes_from_path(const std::filesystem::path &path) { const auto result = ::io::read_bytes_from_path(path); if (!result.has_value()) { - return tl::unexpected(load_error_from_io_error(result.error())); + return std::unexpected(load_error_from_io_error(result.error())); } return result.value(); } -tl::expected, SaveMeshError> save_encoded_to_buffer(const mesh::Encoded &mesh) { +std::expected, SaveMeshError> save_encoded_to_buffer(const mesh::Encoded &mesh) { LOG_TRACE("Serializing mesh to buffer"); // TODO: this ignores the texture format in SaveOptions @@ -66,7 +66,7 @@ tl::expected, SaveMeshError> save_encoded_to_buffer(const m case std::errc::no_buffer_space: case std::errc::message_size: case std::errc::result_out_of_range: - return tl::unexpected(SaveMeshErrorKind::OutOfMemory); + return std::unexpected(SaveMeshErrorKind::OutOfMemory); break; default: UNREACHABLE(); @@ -77,7 +77,7 @@ tl::expected, SaveMeshError> save_encoded_to_buffer(const m return data; } -tl::expected load_encoded_from_buffer(const std::span bytes) { +std::expected load_encoded_from_buffer(const std::span bytes) { LOG_TRACE("Deserializing mesh from buffer"); zpp::bits::in in(bytes); @@ -90,12 +90,12 @@ tl::expected load_encoded_from_buffer(const std::s switch (result) { case std::errc::no_buffer_space: case std::errc::message_size: - return tl::unexpected(LoadMeshErrorKind::OutOfMemory); + return std::unexpected(LoadMeshErrorKind::OutOfMemory); case std::errc::value_too_large: case std::errc::bad_message: case std::errc::protocol_error: case std::errc::result_out_of_range: - return tl::unexpected(LoadMeshErrorKind::InvalidFormat); + return std::unexpected(LoadMeshErrorKind::InvalidFormat); case std::errc::not_supported: case std::errc::invalid_argument: UNREACHABLE(); @@ -109,61 +109,61 @@ tl::expected load_encoded_from_buffer(const std::s } } -tl::expected, SaveMeshError> save_to_buffer(const SimpleMesh &mesh, const SaveOptions& options) { +std::expected, SaveMeshError> save_to_buffer(const SimpleMesh &mesh, const SaveOptions& options) { const auto encode_result = mesh::encode(mesh, mesh::EncodeOptions{ .texture_format = options.texture_format}); if (!encode_result.has_value()) { - return tl::unexpected(SaveMeshErrorKind::UnsupportedFormat); + return std::unexpected(SaveMeshErrorKind::UnsupportedFormat); } const mesh::Encoded encoded = encode_result.value(); const auto deser_result = save_encoded_to_buffer(encoded); if (!deser_result.has_value()) { - return tl::unexpected(deser_result.error()); + return std::unexpected(deser_result.error()); } const std::vector buffer = deser_result.value(); return buffer; } -tl::expected load_from_buffer(const std::span bytes, const LoadOptions & /* options */) { +std::expected load_from_buffer(const std::span bytes, const LoadOptions & /* options */) { const auto deser_result = load_encoded_from_buffer(bytes); if (!deser_result.has_value()) { - return tl::unexpected(deser_result.error()); + return std::unexpected(deser_result.error()); } const mesh::Encoded encoded = deser_result.value(); const auto decode_result = mesh::decode(encoded); if (!decode_result.has_value()) { - return tl::unexpected(LoadMeshErrorKind::InvalidFormat); + return std::unexpected(LoadMeshErrorKind::InvalidFormat); } const mesh::Simple mesh = decode_result.value(); return mesh; } -tl::expected load_from_path(const std::filesystem::path &path, const LoadOptions & /* options */) { +std::expected load_from_path(const std::filesystem::path &path, const LoadOptions & /* options */) { const auto bytes_result = read_bytes_from_path(path); if (!bytes_result.has_value()) { - return tl::unexpected(bytes_result.error()); + return std::unexpected(bytes_result.error()); } const std::vector bytes = bytes_result.value(); return load_from_buffer(bytes); } -tl::expected save_to_path(const SimpleMesh &mesh, const std::filesystem::path &path, const SaveOptions& options) { +std::expected save_to_path(const SimpleMesh &mesh, const std::filesystem::path &path, const SaveOptions& options) { LOG_TRACE("Saving mesh as high precision terrain"); const auto result = save_to_buffer(mesh, options); if (!result.has_value()) { - return tl::unexpected(result.error()); + return std::unexpected(result.error()); } const std::vector bytes = result.value(); const auto write_result = write_bytes_to_path(bytes, path); if (!write_result.has_value()) { - return tl::unexpected(write_result.error()); + return std::unexpected(write_result.error()); } return {}; diff --git a/src/terrainlib/mesh/io/terrain.h b/src/terrainlib/mesh/io/terrain.h index 9c505c14..fd097a24 100644 --- a/src/terrainlib/mesh/io/terrain.h +++ b/src/terrainlib/mesh/io/terrain.h @@ -3,7 +3,7 @@ #include #include -#include +#include #include "mesh/SimpleMesh.h" #include "mesh/io/error.h" @@ -11,10 +11,10 @@ namespace mesh::io::terrain { -tl::expected load_from_path(const std::filesystem::path &path, const LoadOptions &options = {}); -tl::expected load_from_buffer(const std::span buffer, const LoadOptions &options = {}); +std::expected load_from_path(const std::filesystem::path &path, const LoadOptions &options = {}); +std::expected load_from_buffer(const std::span buffer, const LoadOptions &options = {}); -tl::expected save_to_path(const SimpleMesh &mesh, const std::filesystem::path &path, const SaveOptions &options = {}); -tl::expected, SaveMeshError> save_to_buffer(const SimpleMesh &mesh, const SaveOptions &options = {}); +std::expected save_to_path(const SimpleMesh &mesh, const std::filesystem::path &path, const SaveOptions &options = {}); +std::expected, SaveMeshError> save_to_buffer(const SimpleMesh &mesh, const SaveOptions &options = {}); } // namespace mesh::io::terrain diff --git a/src/terrainlib/octree/storage/IndexedStorage.h b/src/terrainlib/octree/storage/IndexedStorage.h index 70076cd3..3ba94e8f 100644 --- a/src/terrainlib/octree/storage/IndexedStorage.h +++ b/src/terrainlib/octree/storage/IndexedStorage.h @@ -45,7 +45,7 @@ class IndexedStorage_ : public Storage_ { void update_index() noexcept { Storage_::update_index(); } - tl::expected save_index() const noexcept { + std::expected save_index() const noexcept { if (!this->is_index_dirty()) { return {}; } diff --git a/src/terrainlib/octree/storage/RawStorage.h b/src/terrainlib/octree/storage/RawStorage.h index 679efb38..39482250 100644 --- a/src/terrainlib/octree/storage/RawStorage.h +++ b/src/terrainlib/octree/storage/RawStorage.h @@ -3,7 +3,7 @@ #include #include -#include +#include #include "octree/Id.h" #include "octree/disk/Layout.h" @@ -29,23 +29,23 @@ class RawStorage_ { RawStorage_(RawStorage_ &&) = default; RawStorage_ &operator=(RawStorage_ &&) = default; - tl::expected load(const Id &id) const noexcept { + std::expected load(const Id &id) const noexcept { const auto path = this->path_for(id); return Codec::load_from_path(path); } - tl::expected save(const Id &id, const T &node) const noexcept { + std::expected save(const Id &id, const T &node) const noexcept { const auto path = this->path_for(id); return Codec::save_to_path(node, path); } - tl::expected copy_to(const Id &id, RawStorage_ &target) const noexcept { + std::expected copy_to(const Id &id, RawStorage_ &target) const noexcept { return target.copy_from(id, *this); } - tl::expected copy_from(const Id &id, const RawStorage_ &source) noexcept { + std::expected copy_from(const Id &id, const RawStorage_ &source) noexcept { if (!source.has(id)) { - return tl::unexpected(CopyErrorKind::FileNotFound); + return std::unexpected(CopyErrorKind::FileNotFound); } const auto source_path = source.path_for(id); @@ -54,13 +54,13 @@ class RawStorage_ { // TODO: should this error instead? const auto load_result = source.load(id); if (!load_result.has_value()) { - return tl::unexpected(CopyErrorKind::Read); + return std::unexpected(CopyErrorKind::Read); } const value_type node = load_result.value(); const auto save_result = this->save(id, node); if (!save_result.has_value()) { - return tl::unexpected(CopyErrorKind::Write); + return std::unexpected(CopyErrorKind::Write); } return {}; } @@ -68,18 +68,18 @@ class RawStorage_ { std::error_code ec; if (std::filesystem::remove(target_path, ec)) { if (ec) { - return tl::unexpected(CopyErrorKind::RemoveOld); + return std::unexpected(CopyErrorKind::RemoveOld); } } std::filesystem::create_directories(target_path.parent_path(), ec); if (ec) { - return tl::unexpected(CopyErrorKind::CreateDirectories); + return std::unexpected(CopyErrorKind::CreateDirectories); } std::filesystem::create_hard_link(source_path, target_path, ec); if (ec) { - return tl::unexpected(CopyErrorKind::CreateLink); + return std::unexpected(CopyErrorKind::CreateLink); } return {}; diff --git a/src/terrainlib/octree/storage/Storage.h b/src/terrainlib/octree/storage/Storage.h index 7fbfc9eb..782a9c03 100644 --- a/src/terrainlib/octree/storage/Storage.h +++ b/src/terrainlib/octree/storage/Storage.h @@ -7,7 +7,7 @@ #include #include -#include +#include #include "mesh/io.h" #include "octree/Id.h" @@ -143,13 +143,13 @@ class Storage_ { } } - tl::expected load(const Id &id) const noexcept { + std::expected load(const Id &id) const noexcept { if (const auto value_opt = this->_cache.get(id)) { return value_opt.value(); } if (!this->_index.contains(id, true)) { - return tl::unexpected(Codec::file_not_found()); + return std::unexpected(Codec::file_not_found()); } const auto result = this->_inner.load(id); @@ -159,7 +159,7 @@ class Storage_ { return result; } - tl::expected save(const Id &id, const value_type &value) noexcept { + std::expected save(const Id &id, const value_type &value) noexcept { if (this->check_overwrite(id)) { LOG_ERROR_AND_EXIT("tried to overwrite value when not allowed"); } @@ -172,9 +172,9 @@ class Storage_ { return result; } - tl::expected copy_from(const Id &id, const Storage_ &source) noexcept { + std::expected copy_from(const Id &id, const Storage_ &source) noexcept { if (!source._index.contains(id, true)) { - return tl::unexpected(CopyErrorKind::FileNotFound); + return std::unexpected(CopyErrorKind::FileNotFound); } if (this->check_overwrite(id)) { @@ -189,7 +189,7 @@ class Storage_ { return result; } - tl::expected copy_to(const Id &id, Storage_ &target) const noexcept { + std::expected copy_to(const Id &id, Storage_ &target) const noexcept { return target.copy_from(id, *this); } @@ -252,7 +252,7 @@ class Storage_ { } } - tl::expected save_or_create_index() noexcept { + std::expected save_or_create_index() noexcept { if (this->is_indexed() && !this->_index.dirty) { return {}; } diff --git a/src/terrainlib/octree/storage/codec/Codec.h b/src/terrainlib/octree/storage/codec/Codec.h index b4c3bc47..f31b6f45 100644 --- a/src/terrainlib/octree/storage/codec/Codec.h +++ b/src/terrainlib/octree/storage/codec/Codec.h @@ -4,7 +4,7 @@ #include #include -#include +#include template concept CodecFor = @@ -16,10 +16,10 @@ concept CodecFor = requires std::same_as; { Codec::load_from_path(path) } noexcept - -> std::same_as>; + -> std::same_as>; { Codec::save_to_path(value, path) } noexcept - -> std::same_as>; + -> std::same_as>; { Codec::file_not_found() } noexcept -> std::same_as; diff --git a/src/terrainlib/octree/storage/codec/DefaultCodec.h b/src/terrainlib/octree/storage/codec/DefaultCodec.h index dd70a27b..dc4acd5a 100644 --- a/src/terrainlib/octree/storage/codec/DefaultCodec.h +++ b/src/terrainlib/octree/storage/codec/DefaultCodec.h @@ -4,7 +4,7 @@ #include #include -#include +#include #include "Codec.h" #include "io/serialize.h" @@ -18,11 +18,11 @@ struct ZppBitsCodec { using load_error = io::Error; using save_error = io::Error; - static tl::expected load_from_path(const std::filesystem::path& path) noexcept { + static std::expected load_from_path(const std::filesystem::path& path) noexcept { return io::read_from_path(path); } - static tl::expected save_to_path(const value_type& value, const std::filesystem::path& path) noexcept { + static std::expected save_to_path(const value_type& value, const std::filesystem::path& path) noexcept { return io::write_to_path(value, path); } diff --git a/src/terrainlib/octree/storage/codec/MeshCodec.h b/src/terrainlib/octree/storage/codec/MeshCodec.h index 2dc65fb6..7a15400a 100644 --- a/src/terrainlib/octree/storage/codec/MeshCodec.h +++ b/src/terrainlib/octree/storage/codec/MeshCodec.h @@ -4,7 +4,7 @@ #include #include -#include +#include #include "mesh/io.h" @@ -15,11 +15,11 @@ struct MeshCodec { using load_error = mesh::io::LoadMeshError; using save_error = mesh::io::SaveMeshError; - static tl::expected load_from_path(const std::filesystem::path& path) noexcept { + static std::expected load_from_path(const std::filesystem::path& path) noexcept { return mesh::io::load_from_path(path); } - static tl::expected save_to_path(const value_type& value, const std::filesystem::path& path) noexcept { + static std::expected save_to_path(const value_type& value, const std::filesystem::path& path) noexcept { return mesh::io::save_to_path(value, path); } diff --git a/src/terrainlib/octree/storage/helpers.cpp b/src/terrainlib/octree/storage/helpers.cpp index 7366be55..ed817515 100644 --- a/src/terrainlib/octree/storage/helpers.cpp +++ b/src/terrainlib/octree/storage/helpers.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include "octree/storage/helpers.h" #include "io/serialize.h" @@ -90,7 +90,7 @@ std::optional guess_layout_strategy( return std::nullopt; } -tl::expected save_index_map(const IndexMap& index, const disk::Layout& layout) { +std::expected save_index_map(const IndexMap& index, const disk::Layout& layout) { const auto index_path = layout.base_path() / disk::v1::index_file_name(); LOG_TRACE("Saving octree storage index to {}", index_path); @@ -102,7 +102,7 @@ tl::expected save_index_map(const IndexMap& index, const disk:: const auto result = io::write_to_path(index_file, index_path); if (!result.has_value()) { LOG_ERROR("Failed to save octree storage index to {}", index_path); - return tl::unexpected(result.error()); + return std::unexpected(result.error()); } return {}; } diff --git a/src/terrainlib/octree/storage/helpers.h b/src/terrainlib/octree/storage/helpers.h index a7dff810..288096cf 100644 --- a/src/terrainlib/octree/storage/helpers.h +++ b/src/terrainlib/octree/storage/helpers.h @@ -4,7 +4,7 @@ #include #include -#include +#include #include "io/Error.h" #include "octree/IndexMap.h" @@ -22,7 +22,7 @@ struct LayoutWithoutBase { std::optional guess_layout_strategy( const std::filesystem::path &base_path, size_t max_files_to_check = 100); -tl::expected save_index_map(const IndexMap &index, const disk::Layout &layout); +std::expected save_index_map(const IndexMap &index, const disk::Layout &layout); void update_index_map(IndexMap &index, const disk::Layout &layout); } diff --git a/src/terrainlib/octree/storage/open.h b/src/terrainlib/octree/storage/open.h index ec212e88..b21ede08 100644 --- a/src/terrainlib/octree/storage/open.h +++ b/src/terrainlib/octree/storage/open.h @@ -4,7 +4,7 @@ #include #include -#include +#include #include "io/Error.h" #include "octree/disk/layout/strategy/Default.h" @@ -21,7 +21,7 @@ struct OpenOptions { }; template Codec = DefaultCodecFor> -tl::expected, io::Error> open_index(const std::filesystem::path &index_path); +std::expected, io::Error> open_index(const std::filesystem::path &index_path); template Codec = DefaultCodecFor> Storage_ open_folder( const std::filesystem::path &base_path, diff --git a/src/terrainlib/octree/storage/open.inl b/src/terrainlib/octree/storage/open.inl index a7cfdb99..a948f4ac 100644 --- a/src/terrainlib/octree/storage/open.inl +++ b/src/terrainlib/octree/storage/open.inl @@ -4,7 +4,7 @@ #include #include -#include +#include #include "io/serialize.h" #include "log.h" @@ -19,13 +19,13 @@ namespace octree { template Codec> -tl::expected, io::Error> open_index(const std::filesystem::path &index_path) { +std::expected, io::Error> open_index(const std::filesystem::path &index_path) { LOG_TRACE("Opening storage index {}", index_path); const auto result = io::read_from_path(index_path); if (!result.has_value()) { LOG_TRACE("Failed to open storage index due to {}", result.error()); - return tl::unexpected(result.error()); + return std::unexpected(result.error()); } auto index_file = result.value(); LOG_TRACE("Successfully read storage index with {} entries.", index_file.map.size()); diff --git a/src/terrainlib/pch.h b/src/terrainlib/pch.h index dce9cf57..dfbd471c 100644 --- a/src/terrainlib/pch.h +++ b/src/terrainlib/pch.h @@ -34,7 +34,7 @@ #include #include #include -#include +#include #include // Internal headers diff --git a/src/terrainlib/srs.h b/src/terrainlib/srs.h index af8308f5..a5e5ce5c 100644 --- a/src/terrainlib/srs.h +++ b/src/terrainlib/srs.h @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include namespace srs { @@ -352,19 +352,19 @@ inline radix::geometry::Aabb3d encompassing_bounds_transfer( return encompassing_bounds_transfer(transform.get(), source_bounds, intermediate_points_edges, intermediate_points_faces); } -inline tl::expected from_epsg(const uint32_t epsg) { +inline std::expected from_epsg(const uint32_t epsg) { OGRSpatialReference srs; if (srs.importFromEPSG(epsg) != OGRERR_NONE) { - return tl::unexpected(fmt::format("Failed to import spatial reference from EPSG code: {}", epsg)); + return std::unexpected(fmt::format("Failed to import spatial reference from EPSG code: {}", epsg)); } srs.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); return srs; } -inline tl::expected from_user_input(const std::string &user_input) { +inline std::expected from_user_input(const std::string &user_input) { OGRSpatialReference srs; if (srs.SetFromUserInput(user_input.c_str()) != OGRERR_NONE) { - return tl::unexpected(fmt::format("Failed to set spatial reference from user input: {}", user_input)); + return std::unexpected(fmt::format("Failed to set spatial reference from user input: {}", user_input)); } srs.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); return srs; diff --git a/src/terrainlib/uv/unwrap.cpp b/src/terrainlib/uv/unwrap.cpp index 5e61092b..206eb285 100644 --- a/src/terrainlib/uv/unwrap.cpp +++ b/src/terrainlib/uv/unwrap.cpp @@ -124,7 +124,7 @@ void clamp_uv_map(UvMap &map, size_t vertex_count) { } } -tl::expected parameterize_mesh(cgal::Mesh &mesh, Algorithm algorithm, Border border) { +std::expected parameterize_mesh(cgal::Mesh &mesh, Algorithm algorithm, Border border) { const cgal::HalfedgeDescriptor bhd = CGAL::Polygon_mesh_processing::longest_border(mesh).first; DEBUG_ASSERT(bhd != boost::graph_traits::null_halfedge()); @@ -173,7 +173,7 @@ tl::expected parameterize_mesh(cgal::Mesh &mesh, Algorit } if (result != CGAL::Surface_mesh_parameterization::OK) { - return tl::unexpected(UnwrapError(result)); + return std::unexpected(UnwrapError(result)); } const auto vertex_count = CGAL::num_vertices(mesh); @@ -201,7 +201,7 @@ std::vector decode_uv_map(const UvMap &map, size_t vertex_count) { } } -tl::expected unwrap( +std::expected unwrap( const std::span triangles, const std::span positions, Algorithm algorithm, @@ -213,7 +213,7 @@ tl::expected unwrap( cgal::Mesh cgal_mesh = convert::to_cgal_mesh(mesh); auto result = parameterize_mesh(cgal_mesh, algorithm, border); if (!result) { - return tl::unexpected(result.error()); + return std::unexpected(result.error()); } const CgalUvMap cgal_uv_map = result.value(); const Map uv_map = decode_uv_map(cgal_uv_map, mesh.vertex_count()); diff --git a/src/terrainlib/uv/unwrap.h b/src/terrainlib/uv/unwrap.h index 53e0fc3a..8a9a564d 100644 --- a/src/terrainlib/uv/unwrap.h +++ b/src/terrainlib/uv/unwrap.h @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -51,13 +51,13 @@ using Texture = cv::Mat; inline constexpr Algorithm DEFAULT_ALGORITHM = Algorithm::TutteBarycentricMapping; inline constexpr Border DEFAULT_BORDER = Border::Circle; -tl::expected unwrap( +std::expected unwrap( const std::span triangles, const std::span positions, Algorithm algorithm = DEFAULT_ALGORITHM, Border border = DEFAULT_BORDER); -inline tl::expected unwrap( +inline std::expected unwrap( const std::vector& triangles, const std::vector& positions, Algorithm algorithm = DEFAULT_ALGORITHM, @@ -69,7 +69,7 @@ inline tl::expected unwrap( border); } -inline tl::expected unwrap( +inline std::expected unwrap( const mesh::View &mesh, Algorithm algorithm = DEFAULT_ALGORITHM, Border border = DEFAULT_BORDER) { @@ -80,7 +80,7 @@ inline tl::expected unwrap( border); } -inline tl::expected unwrap( +inline std::expected unwrap( const mesh::Simple &mesh, Algorithm algorithm = DEFAULT_ALGORITHM, Border border = DEFAULT_BORDER) { diff --git a/src/tile_builder/CMakeLists.txt b/src/tile_builder/CMakeLists.txt index d4464342..576df41c 100644 --- a/src/tile_builder/CMakeLists.txt +++ b/src/tile_builder/CMakeLists.txt @@ -1,7 +1,7 @@ add_library(tilebuilderlib alpine_raster.cpp DatasetReader.cpp - Image.cpp + image_writer.cpp ParallelTileGenerator.cpp ParallelTiler.cpp TileHeightsGenerator.cpp diff --git a/src/tile_builder/DatasetReader.cpp b/src/tile_builder/DatasetReader.cpp index bad96340..a472ae54 100644 --- a/src/tile_builder/DatasetReader.cpp +++ b/src/tile_builder/DatasetReader.cpp @@ -29,7 +29,7 @@ #include "Dataset.h" #include "Exception.h" -#include "Image.h" +#include #include "ctb/types.hpp" #include "log.h" @@ -185,12 +185,12 @@ DatasetReader::DatasetReader(const std::shared_ptr& dataset, const OGRS throw Exception(fmt::format("Dataset does not contain band number {} (there are {} bands).", band, dataset->n_bands())); } -HeightData DatasetReader::read(const radix::tile::SrsBounds& bounds, unsigned width, unsigned height) const +radix::Raster DatasetReader::read(const radix::tile::SrsBounds& bounds, unsigned width, unsigned height) const { return readFrom(m_dataset, bounds, width, height); } -HeightData DatasetReader::readWithOverviews(const radix::tile::SrsBounds& bounds, unsigned width, unsigned height) const +radix::Raster DatasetReader::readWithOverviews(const radix::tile::SrsBounds& bounds, unsigned width, unsigned height) const { #ifdef ALP_ENABLE_OVERVIEW_READING auto transformer_args = make_image_transform_args(*this, m_dataset.get(), bounds, width, height); @@ -202,7 +202,7 @@ HeightData DatasetReader::readWithOverviews(const radix::tile::SrsBounds& bounds #endif } -HeightData DatasetReader::readFrom(const std::shared_ptr& source_dataset, const radix::tile::SrsBounds& bounds, unsigned width, unsigned height) const +radix::Raster DatasetReader::readFrom(const std::shared_ptr& source_dataset, const radix::tile::SrsBounds& bounds, unsigned width, unsigned height) const { // if we have performance problems with the warping, it'd still be possible to approximate the warping operation with a linear transform (mostly when zoomed in / on higher zoom levels). // CTB does this in GDALTiler.cpp around line 375 ("// Decide if we are doing an approximate or exact transformation"). @@ -212,9 +212,9 @@ HeightData DatasetReader::readFrom(const std::shared_ptr& source_datase auto warped_dataset = Dataset(static_cast(GDALCreateWarpedVRT(source_dataset->gdalDataset(), int(width), int(height), adfGeoTransform.data(), warp_options.first.get()))); auto* heights_band = warped_dataset.gdalDataset()->GetRasterBand(1); // non-owning pointer - auto heights_data = HeightData(width, height); + auto heights_data = radix::Raster({ width, height }); if (heights_band->RasterIO(GF_Read, 0, 0, int(width), int(height), - static_cast(heights_data.data()), int(width), int(height), GDT_Float32, 0, 0) + static_cast(heights_data.buffer().data()), int(width), int(height), GDT_Float32, 0, 0) != CE_None) throw Exception("couldn't read data"); diff --git a/src/tile_builder/DatasetReader.h b/src/tile_builder/DatasetReader.h index e75b1ff8..a60400c6 100644 --- a/src/tile_builder/DatasetReader.h +++ b/src/tile_builder/DatasetReader.h @@ -23,7 +23,7 @@ #include #include -#include "Image.h" +#include #include class Dataset; @@ -33,8 +33,8 @@ class DatasetReader { public: DatasetReader(const std::shared_ptr& dataset, const OGRSpatialReference& targetSRS, unsigned band, bool warn_on_missing_overviews = true); - HeightData read(const radix::tile::SrsBounds& bounds, unsigned width, unsigned height) const; - HeightData readWithOverviews(const radix::tile::SrsBounds& bounds, unsigned width, unsigned height) const; + radix::Raster read(const radix::tile::SrsBounds& bounds, unsigned width, unsigned height) const; + radix::Raster readWithOverviews(const radix::tile::SrsBounds& bounds, unsigned width, unsigned height) const; unsigned dataset_band() const { return m_band; } bool isReprojecting() const { return m_requires_reprojection; } @@ -42,7 +42,7 @@ class DatasetReader { std::string target_srs_wkt() const { return m_target_srs_wkt; } protected: - HeightData readFrom(const std::shared_ptr& dataset, const radix::tile::SrsBounds& bounds, unsigned width, unsigned height) const; + radix::Raster readFrom(const std::shared_ptr& dataset, const radix::tile::SrsBounds& bounds, unsigned width, unsigned height) const; private: std::shared_ptr m_dataset; diff --git a/src/tile_builder/Image.cpp b/src/tile_builder/Image.cpp deleted file mode 100644 index 3b33465b..00000000 --- a/src/tile_builder/Image.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include -#include "Image.h" -#include - -void image::saveImageAsPng(const Image& inputImage, const std::string& path) -{ - const int width = static_cast(inputImage.width()); - const int height = static_cast(inputImage.height()); - - cv::Mat image(height, width, CV_8UC3); - - for (int row = 0; row < height; ++row) { - for (int col = 0; col < width; ++col) { - const glm::u8vec3& pixel = inputImage.pixel(height - row - 1, col); // Flip vertically - image.at(row, col) = cv::Vec3b(pixel.z, pixel.y, pixel.x); // RGB to BGR - } - } - - cv::imwrite(path, image); -} diff --git a/src/tile_builder/Image.h b/src/tile_builder/Image.h deleted file mode 100644 index 5785329d..00000000 --- a/src/tile_builder/Image.h +++ /dev/null @@ -1,105 +0,0 @@ -/***************************************************************************** - * Alpine Terrain Builder - * Copyright (C) 2022 alpinemaps.org - * Copyright (C) 2022 Adam Celarek - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - *****************************************************************************/ - -#ifndef IMAGE_H -#define IMAGE_H - -#include -#include -#include -#include -#include -#include - -#include - -namespace tntn { -template -class Raster; -} - -template -class Image { -public: - Image() = default; - Image(unsigned width, unsigned height) - : m_width(width) - , m_height(height) - , m_data(size_t(m_width * m_height)) - { - } - - [[nodiscard]] unsigned width() const { return m_width; } - [[nodiscard]] unsigned height() const { return m_height; } - [[nodiscard]] T pixel(unsigned row, unsigned column) const - { - assert(column < m_width); - assert(row < m_height); - assert(m_data.size() == size_t(m_width * m_height)); - return m_data[row * m_width + column]; - } - - [[nodiscard]] float* data() { return m_data.data(); } - - [[nodiscard]] auto size() const { return m_data.size(); } - [[nodiscard]] auto begin() { return m_data.begin(); } - [[nodiscard]] auto end() { return m_data.end(); } - [[nodiscard]] auto begin() const { return m_data.begin(); } - [[nodiscard]] auto end() const { return m_data.end(); } - -private: - unsigned m_width = 0; - unsigned m_height = 0; - std::vector m_data; - - friend class tntn::Raster; -}; - -using HeightData = Image; -using RgbImage = Image; -using uchar = unsigned char; - -namespace image { -void saveImageAsPng(const Image& image, const std::string& path); - -template -[[nodiscard]] auto transformImage(const Image& i, Fun conversion_fun) -> Image -{ - using T2 = decltype(conversion_fun(*i.begin())); - Image i2(i.width(), i.height()); - std::transform(i.begin(), i.end(), i2.begin(), conversion_fun); - return i2; -} - -template -void debugOut(const Image& image, const std::string& path) -{ - auto [min, max] = std::ranges::minmax(image); - - // [min=min, ..] is required for cpp correctness. min/max from the capture are not variables, we need to copy them: - // https://stackoverflow.com/questions/50799719/reference-to-local-binding-declared-in-enclosing-function?noredirect=1&lq=1 - saveImageAsPng(transformImage(image, [min = min, max = max](auto v) { - const auto c = uchar(255.F * (float(v) - float(min)) / float(max - min)); - return glm::u8vec3(c, c, c); - }), - path); -} -} - -#endif // HEIGHTDATA_H diff --git a/src/tile_builder/ParallelTileGenerator.cpp b/src/tile_builder/ParallelTileGenerator.cpp index e0d7e731..70924640 100644 --- a/src/tile_builder/ParallelTileGenerator.cpp +++ b/src/tile_builder/ParallelTileGenerator.cpp @@ -64,7 +64,7 @@ const ctb::Grid& ParallelTileGenerator::grid() const return m_grid; } -void ParallelTileGenerator::write(const radix::tile::Descriptor& tile, const HeightData& heights) const +void ParallelTileGenerator::write(const radix::tile::Descriptor& tile, const radix::Raster& heights) const { const auto dir_path = fmt::format("{}/{}/{}", m_output_data_path, tile.id.zoom_level, tile.id.coords.x); const auto file_path = fmt::format("{}/{}.{}", dir_path, tile.id.coords.y, m_tile_writer->formatFileEnding()); diff --git a/src/tile_builder/ParallelTileGenerator.h b/src/tile_builder/ParallelTileGenerator.h index f95a412f..f9fa61e0 100644 --- a/src/tile_builder/ParallelTileGenerator.h +++ b/src/tile_builder/ParallelTileGenerator.h @@ -22,7 +22,7 @@ #include #include -#include "Image.h" +#include #include "ParallelTiler.h" #include "ctb/Grid.hpp" #include "ctb/types.hpp" @@ -49,7 +49,7 @@ class ParallelTileGenerator { void setWarnOnMissingOverviews(bool flag) { m_warn_on_missing_overviews = flag; } [[nodiscard]] const ParallelTiler& tiler() const; [[nodiscard]] const ctb::Grid& grid() const; - void write(const radix::tile::Descriptor& tile, const HeightData& heights) const; + void write(const radix::tile::Descriptor& tile, const radix::Raster& heights) const; void process(const std::pair& zoom_range, bool progress_bar_on_console = false, bool generate_world_wide_tiles = false) const; }; @@ -68,7 +68,7 @@ class ParallelTileWriterInterface { virtual ~ParallelTileWriterInterface() = default; ParallelTileWriterInterface& operator=(const ParallelTileWriterInterface&) = default; ParallelTileWriterInterface& operator=(ParallelTileWriterInterface&&) = default; - virtual void write(const std::string& file_path, const radix::tile::Descriptor& tile, const HeightData& heights) const = 0; + virtual void write(const std::string& file_path, const radix::tile::Descriptor& tile, const radix::Raster& heights) const = 0; [[nodiscard]] radix::tile::Border formatRequiresBorder() const; [[nodiscard]] const std::string& formatFileEnding() const; }; diff --git a/src/tile_builder/algorithms/primitives.h b/src/tile_builder/algorithms/primitives.h deleted file mode 100644 index a6101364..00000000 --- a/src/tile_builder/algorithms/primitives.h +++ /dev/null @@ -1,103 +0,0 @@ -#include -#include -#include - -#ifndef ALGORITHMS_PRIMITIVES_H -#define ALGORITHMS_PRIMITIVES_H - -namespace primitives { - -// two times the area of the ccw triangle with vertices a, b, and c. -// negative, if the triangle is cw -template -inline T triAreaX2(const glm::tvec2& a, const glm::tvec2& b, const glm::tvec2& c) -{ - // doesn't work for unsigned types. if you need that, come up with something :) - static_assert(std::is_signed_v); - return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); -} - -enum class Winding : char { - CW = -1, - Undefined = 0, - CCW = 1 -}; - -template -inline Winding winding(const glm::tvec2& a, const glm::tvec2& b, const glm::tvec2& c) -{ - constexpr bool is_integral = std::is_integral_v; - if constexpr (is_integral) { - using Signed = typename std::conditional::type, T>::type; - auto v = (Signed(b.x) - Signed(a.x)) * (Signed(c.y) - Signed(a.y)) - (Signed(b.y) - Signed(a.y)) * (Signed(c.x) - Signed(a.x)); - if (v == 0) - return Winding::Undefined; - if (v < 0) - return Winding::CW; - return Winding::CCW; - } else { - auto v = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); - if (std::abs(v) < 0.0000000000000001) - return Winding::Undefined; - if (v < 0) - return Winding::CW; - return Winding::CCW; - } -} - -template -inline bool ccw(const glm::tvec2& a, const glm::tvec2& b, const glm::tvec2& c) -{ - constexpr bool is_integral = std::is_integral_v; - if constexpr (is_integral) { - using Signed = typename std::conditional::type, T>::type; - if constexpr (include_border) - return (Signed(b.x) - Signed(a.x)) * (Signed(c.y) - Signed(a.y)) >= (Signed(b.y) - Signed(a.y)) * (Signed(c.x) - Signed(a.x)); - return (Signed(b.x) - Signed(a.x)) * (Signed(c.y) - Signed(a.y)) > (Signed(b.y) - Signed(a.y)) * (Signed(c.x) - Signed(a.x)); - } - if constexpr (include_border) - return (b.x - a.x) * (c.y - a.y) >= (b.y - a.y) * (c.x - a.x); - return (b.x - a.x) * (c.y - a.y) > (b.y - a.y) * (c.x - a.x); -} - -template -inline bool rightOf(const glm::tvec2& x, const glm::tvec2& org, const glm::tvec2& dest) -{ - return ccw(x, dest, org); -} - -template -inline bool leftOf(const glm::tvec2& x, const glm::tvec2& org, const glm::tvec2& dest) -{ - return ccw(x, org, dest); -} - -// https://www.scratchapixel.com/lessons/3d-basic-rendering/rasterization-practical-implementation/rasterization-stage -// the bottom edge of the raster is not included! -template -inline bool inside(const glm::tvec2& x, const glm::tvec2& a, const glm::tvec2& b, const glm::tvec2& c) -{ - constexpr bool is_integral = std::is_integral_v; - using Signed = typename std::conditional::type, T>::type; - using sVec = glm::tvec2; - // return leftOf(x, a, b) && leftOf(x, b, c) && leftOf(x, c, a); - const auto ccw = [](Winding w) { return w == Winding::CCW; }; - const auto undef = [](Winding w) { return w == Winding::Undefined; }; - const auto topleftedge = [](const sVec& edge) { return (edge.y == 0 && edge.x < 0) || edge.y < 0; }; - - const auto w_ab = winding(x, a, b); - const auto w_bc = winding(x, b, c); - const auto w_ca = winding(x, c, a); - const auto e_ab = sVec(b) - sVec(a); - const auto e_bc = sVec(c) - sVec(b); - const auto e_ca = sVec(a) - sVec(c); - - bool overlap = true; - overlap &= ccw(w_ab) || (undef(w_ab) && topleftedge(e_ab)); - overlap &= ccw(w_bc) || (undef(w_bc) && topleftedge(e_bc)); - overlap &= ccw(w_ca) || (undef(w_ca) && topleftedge(e_ca)); - return overlap; -} -} - -#endif diff --git a/src/tile_builder/algorithms/raster_triangle_scanline.h b/src/tile_builder/algorithms/raster_triangle_scanline.h deleted file mode 100644 index 7e22fdca..00000000 --- a/src/tile_builder/algorithms/raster_triangle_scanline.h +++ /dev/null @@ -1,101 +0,0 @@ -/***************************************************************************** - * Alpine Terrain Builder - * Copyright (C) 2022 alpinemaps.org - * Copyright (C) 2022 Adam Celarek - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - *****************************************************************************/ - -#ifndef ALGORITHMS_RASTER_TRIANGLE_SCANLINE_H -#define ALGORITHMS_RASTER_TRIANGLE_SCANLINE_H - -#include "primitives.h" -#include "tntn/Raster.h" -#include -#include -#include - -namespace raster { - -template -void triangle_scanline(const tntn::Raster& raster, const glm::uvec2& a, const glm::uvec2& b, const glm::uvec2& c, const Lambda& fun) -{ - assert(primitives::winding(a, b, c) == primitives::Winding::CCW); - - const auto min = glm::min(glm::min(a, b), c); - const auto max = glm::max(glm::max(a, b), c); - for (auto y = min.y; y <= max.y; ++y) { - for (auto x = min.x; x <= max.x; ++x) { - const auto coord = glm::uvec2(x, y); - if (primitives::inside(coord, a, b, c)) - fun(coord, raster.value(coord.y, coord.x)); // raster is row / column - } - } - if (min.y == 0) { - // bottom row of raster is not included in primitives::inside, so check for it and make an extrawurscht. - const auto walk_bottom = [&](const glm::uvec2& a, const glm::uvec2& b) { - if ((b - a).y == 0 && a.y == 0) { - const auto end_x = std::max(a.x, b.x); - for (auto x = std::min(a.x, b.x); x < end_x; ++x) { - const auto coord = glm::uvec2(x, 0); - fun(coord, raster.value(coord.y, coord.x)); // raster is row / column - } - } - }; - walk_bottom(a, b); - walk_bottom(b, c); - walk_bottom(c, a); - } - const auto last_x = raster.get_width() - 1; - if (max.x == last_x) { - // similar with the rightmost column - const auto walk_right = [&](const glm::uvec2& a, const glm::uvec2& b) { - if ((b - a).x == 0 && a.x == last_x) { - const auto end_y = std::max(a.y, b.y); - for (auto y = std::min(a.y, b.y); y < end_y; ++y) { - const auto coord = glm::uvec2(last_x, y); - fun(coord, raster.value(coord.y, coord.x)); // raster is row / column - } - } - }; - walk_right(a, b); - walk_right(b, c); - walk_right(c, a); - } - // if (max.x == last_x && min.y == 0) { - // const auto check_br = [&](const glm::uvec2& a, const glm::uvec2& b) { - // if ((b - a).y == 0 && a.y == 0 && b.x == last_x) { - // const auto coord = glm::uvec2(last_x, 0); - // fun(coord, raster.value(coord.y, coord.x)); // raster is row / column - // } - // }; - // check_br(a, b); - // check_br(b, c); - // check_br(c, a); - // } - const auto last_y = raster.get_height() - 1; - if (max.x == last_x && max.y == last_y) { - const auto check_tr = [&](const glm::uvec2& a, const glm::uvec2& b) { - if ((b - a).x == 0 && b.y == last_y && b.x == last_x) { - const auto coord = glm::uvec2(last_x, last_y); - fun(coord, raster.value(coord.y, coord.x)); // raster is row / column - } - }; - check_tr(a, b); - check_tr(b, c); - check_tr(c, a); - } -} -} -#endif diff --git a/src/tile_builder/alpine_raster.cpp b/src/tile_builder/alpine_raster.cpp index b9ce5353..be4e0e1d 100644 --- a/src/tile_builder/alpine_raster.cpp +++ b/src/tile_builder/alpine_raster.cpp @@ -25,9 +25,10 @@ #include #include -#include "Image.h" #include "ParallelTileGenerator.h" #include "ctb/Grid.hpp" +#include "image_writer.h" +#include #include ParallelTileGenerator alpine_raster::make_generator(const std::string& input_data_path, const std::string& output_data_path, ctb::Grid::Srs srs, radix::tile::Scheme tiling_scheme, radix::tile::Border border, unsigned grid_resolution) @@ -35,8 +36,7 @@ ParallelTileGenerator alpine_raster::make_generator(const std::string& input_dat return ParallelTileGenerator::make(input_data_path, srs, tiling_scheme, std::make_unique(border), output_data_path, grid_resolution); } -void alpine_raster::TileWriter::write(const std::string& file_path, const radix::tile::Descriptor&, const HeightData& heights) const +void alpine_raster::TileWriter::write(const std::string& file_path, const radix::tile::Descriptor&, const radix::Raster& heights) const { - image::saveImageAsPng(image::transformImage(heights, radix::height_encoding::to_rgb), - file_path); + image::saveImageAsPng(radix::raster::transform(heights, radix::height_encoding::to_rgb), file_path); } diff --git a/src/tile_builder/alpine_raster.h b/src/tile_builder/alpine_raster.h index 966d55fb..22eee044 100644 --- a/src/tile_builder/alpine_raster.h +++ b/src/tile_builder/alpine_raster.h @@ -25,7 +25,7 @@ #include #include -#include "Image.h" +#include #include "ParallelTileGenerator.h" #include #include "ctb/Grid.hpp" @@ -38,7 +38,7 @@ class TileWriter : public ParallelTileWriterInterface { : ParallelTileWriterInterface(border, "png") { } - void write(const std::string& base_path, const radix::tile::Descriptor& tile, const HeightData& heights) const override; + void write(const std::string& base_path, const radix::tile::Descriptor& tile, const radix::Raster& heights) const override; }; [[nodiscard]] ParallelTileGenerator make_generator( const std::string& input_data_path, diff --git a/src/tile_builder/image_writer.cpp b/src/tile_builder/image_writer.cpp new file mode 100644 index 00000000..725cdfae --- /dev/null +++ b/src/tile_builder/image_writer.cpp @@ -0,0 +1,46 @@ +/***************************************************************************** + * Alpine Terrain Builder + * Copyright (C) 2022 alpinemaps.org + * Copyright (C) 2022 Adam Celarek + * Copyright (C) 2025 Martin Braunsperger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *****************************************************************************/ + +#include "image_writer.h" + +#include +#include + +void image::saveImageAsPng(const radix::Raster& input_image, const std::string& path) +{ + const int width = static_cast(input_image.width()); + const int height = static_cast(input_image.height()); + + cv::Mat image(height, width, CV_8UC3); + + for (int row = 0; row < height; ++row) { + for (int column = 0; column < width; ++column) { + const auto& pixel = input_image.pixel({ static_cast(column), static_cast(height - row - 1) }); + image.at(row, column) = cv::Vec3b(pixel.z, pixel.y, pixel.x); + } + } + + try { + if (!cv::imwrite(path, image)) + throw std::runtime_error("Failed to write PNG image to " + path); + } catch (const cv::Exception& error) { + throw std::runtime_error("Failed to write PNG image to " + path + ": " + error.what()); + } +} diff --git a/src/tile_builder/image_writer.h b/src/tile_builder/image_writer.h new file mode 100644 index 00000000..db8ff8f9 --- /dev/null +++ b/src/tile_builder/image_writer.h @@ -0,0 +1,51 @@ +/***************************************************************************** + * Alpine Terrain Builder + * Copyright (C) 2022 alpinemaps.org + * Copyright (C) 2022 Adam Celarek + * Copyright (C) 2025 Martin Braunsperger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +namespace image { + +void saveImageAsPng(const radix::Raster& image, const std::string& path); + +template +void debugOut(const radix::Raster& image, const std::string& path) +{ + if (image.buffer().empty()) + throw std::invalid_argument("Can't write an empty raster to " + path); + + const auto [min, max] = std::ranges::minmax(image); + const auto range = float(max) - float(min); + saveImageAsPng(radix::raster::transform(image, [min, range](const auto value) { + const auto intensity = range == 0.F ? std::uint8_t(0) : std::uint8_t(255.F * (float(value) - float(min)) / range); + return glm::u8vec3(intensity); + }), + path); +} + +} // namespace image diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index cbf59ea8..647863fe 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -66,7 +66,7 @@ if(TARGET tilebuilderlib) tilebuilder/alpine_raster_format.cpp tilebuilder/dataset_reading.cpp tilebuilder/depth_first_tile_traverser.cpp - tilebuilder/image.cpp + tilebuilder/image_writer.cpp tilebuilder/parallel_tile_generator.cpp tilebuilder/parallel_tiler.cpp tilebuilder/tile_heights_generator.cpp diff --git a/unittests/terrainlib/mesh_io.cpp b/unittests/terrainlib/mesh_io.cpp index bac6225b..cb117366 100644 --- a/unittests/terrainlib/mesh_io.cpp +++ b/unittests/terrainlib/mesh_io.cpp @@ -44,14 +44,14 @@ TEST_CASE("transcode roundtrip") { mesh.texture = cv::Mat3b(100, 100); cv::randu(*mesh.texture, cv::Scalar(0, 0, 0), cv::Scalar(256, 256, 256)); - const tl::expected encode_result = + const std::expected encode_result = mesh::encode(mesh, mesh::EncodeOptions{.texture_format = ".png"}); if (!encode_result.has_value()) { FAIL(encode_result.error()); } const mesh::Encoded encoded = encode_result.value(); - const tl::expected decode_result = + const std::expected decode_result = mesh::decode(encoded, mesh::DecodeOptions{}); if (!decode_result.has_value()) { FAIL(decode_result.error()); @@ -93,7 +93,7 @@ TEST_CASE("io roundtrip") { mesh::io::save_to_path(mesh, mesh_path, mesh::io::SaveOptions{.texture_format = ".png"}); CHECK(std::filesystem::exists(mesh_path)); - const tl::expected result = mesh::io::load_from_path(mesh_path); + const std::expected result = mesh::io::load_from_path(mesh_path); if (!result.has_value()) { FAIL(result.error().description()); } @@ -136,7 +136,7 @@ TEST_CASE("io roundtrip high precision") { mesh::io::save_to_path(mesh, mesh_path, mesh::io::SaveOptions{.texture_format = ".png"}); CHECK(std::filesystem::exists(mesh_path)); - const tl::expected result = mesh::io::load_from_path(mesh_path); + const std::expected result = mesh::io::load_from_path(mesh_path); if (!result.has_value()) { FAIL(result.error().description()); } @@ -179,7 +179,7 @@ TEST_CASE("io roundtrip no texture") { mesh::io::save_to_path(mesh, mesh_path); CHECK(std::filesystem::exists(mesh_path)); - const tl::expected result = mesh::io::load_from_path(mesh_path); + const std::expected result = mesh::io::load_from_path(mesh_path); if (!result.has_value()) { FAIL(result.error().description()); } @@ -216,7 +216,7 @@ TEST_CASE("io roundtrip no texture and uvs") { mesh::io::save_to_path(mesh, mesh_path); CHECK(std::filesystem::exists(mesh_path)); - const tl::expected result = mesh::io::load_from_path(mesh_path); + const std::expected result = mesh::io::load_from_path(mesh_path); if (!result.has_value()) { FAIL(result.error().description()); } diff --git a/unittests/tilebuilder/alpine_raster_format.cpp b/unittests/tilebuilder/alpine_raster_format.cpp index 00ec9bf4..5c5cafd4 100644 --- a/unittests/tilebuilder/alpine_raster_format.cpp +++ b/unittests/tilebuilder/alpine_raster_format.cpp @@ -24,7 +24,7 @@ #include #include -#include "Image.h" +#include #include "alpine_raster.h" #include "ctb/Grid.hpp" @@ -42,14 +42,14 @@ TEMPLATE_TEST_CASE("alpine raster format, border ", "", std::true_type, std::fal SECTION("raste write") { const auto generator = alpine_raster::make_generator(ALP_TEST_DATA_DIR "/austria/at_mgi.tif", "./unittest_tiles/", ctb::Grid::Srs::SphericalMercator, radix::tile::Scheme::Tms, radix::tile::Border::Yes); - generator.write(radix::tile::Descriptor { {0, glm::uvec2(0, 0)}, {}, int(ctb::Grid::Srs::SphericalMercator), 256, 257 }, HeightData(257, 257)); + generator.write(radix::tile::Descriptor { {0, glm::uvec2(0, 0)}, {}, int(ctb::Grid::Srs::SphericalMercator), 256, 257 }, radix::Raster({ 257, 257 })); CHECK(std::filesystem::exists("./unittest_tiles/0/0/0.png")); - generator.write(radix::tile::Descriptor { {1, glm::uvec2(2, 3)}, {}, int(ctb::Grid::Srs::SphericalMercator), 256, 257 }, HeightData(257, 257)); + generator.write(radix::tile::Descriptor { {1, glm::uvec2(2, 3)}, {}, int(ctb::Grid::Srs::SphericalMercator), 256, 257 }, radix::Raster({ 257, 257 })); CHECK(std::filesystem::exists("./unittest_tiles/1/2/3.png")); // check that a second write doesn't crash - generator.write(radix::tile::Descriptor { {1, glm::uvec2(2, 3)}, {}, int(ctb::Grid::Srs::SphericalMercator), 256, 257 }, HeightData(257, 257)); + generator.write(radix::tile::Descriptor { {1, glm::uvec2(2, 3)}, {}, int(ctb::Grid::Srs::SphericalMercator), 256, 257 }, radix::Raster({ 257, 257 })); CHECK(std::filesystem::exists("./unittest_tiles/1/2/3.png")); // in the best case, we would read back the data and check it. but that's too much work for now. diff --git a/unittests/tilebuilder/dataset_reading.cpp b/unittests/tilebuilder/dataset_reading.cpp index 17c714e9..ba2804db 100644 --- a/unittests/tilebuilder/dataset_reading.cpp +++ b/unittests/tilebuilder/dataset_reading.cpp @@ -27,6 +27,7 @@ #include "Dataset.h" #include "DatasetReader.h" #include "ctb/types.hpp" +#include "image_writer.h" #include "srs.h" using namespace radix; @@ -184,7 +185,7 @@ TEST_CASE("reading") if (ALP_UNITTESTS_DEBUG_IMAGES) { image::debugOut(ref_heights, fmt::format("./heights_{}_{}.png", test_name, dataset_name.substr(s, l))); - auto height_diffs = HeightData(render_width, render_height); + auto height_diffs = radix::Raster({ render_width, render_height }); std::transform(ref_heights.begin(), ref_heights.end(), heights.begin(), height_diffs.begin(), [](auto a, auto b) { return std::abs(a - b); }); const auto path = fmt::format("./diffs_{}_{}.png", test_name, dataset_name.substr(s, l)); image::debugOut(height_diffs, path); @@ -194,7 +195,7 @@ TEST_CASE("reading") const auto t = std::abs(double(a) - double(b)); largest_abs_diff = std::max(t, largest_abs_diff); return t * t; - }) / double(ref_heights.size()); + }) / double(ref_heights.buffer_length()); // fmt::print("{} | {}; mse: {}, largest_abs_diff: {}\n", test_name, dataset_name.substr(s, l), mse, largest_abs_diff); CHECK(largest_abs_diff < double(max_abs_diff)); CHECK(mse < max_mse); @@ -248,7 +249,7 @@ TEST_CASE("reading") image::debugOut(low_res_heights, fmt::format("./low_res_heights.png")); image::debugOut(high_res_heights, fmt::format("./high_res_heights.png")); - auto height_diffs = HeightData(render_width, render_height); + auto height_diffs = radix::Raster({ render_width, render_height }); std::transform(low_res_heights.begin(), low_res_heights.end(), high_res_heights.begin(), height_diffs.begin(), [](auto a, auto b) { return std::abs(a - b); }); image::debugOut(height_diffs, "./diff_low_res_high_res.png"); } @@ -257,7 +258,7 @@ TEST_CASE("reading") const auto t = std::abs(double(a) - double(b)); largest_abs_diff = std::max(t, largest_abs_diff); return t * t; - }) / double(low_res_heights.size()); + }) / double(low_res_heights.buffer_length()); // fmt::print("mse: {}, largest_abs_diff: {}\n", mse, largest_abs_diff); CHECK(largest_abs_diff < double(max_abs_diff)); CHECK(mse < max_mse); @@ -290,7 +291,7 @@ TEST_CASE("reading") image::debugOut(low_res_heights, fmt::format("./ov_with_warping_low_res_heights.png")); image::debugOut(high_res_heights, fmt::format("./ov_with_warping_high_res_heights.png")); - auto height_diffs = HeightData(render_width, render_height); + auto height_diffs = radix::Raster({ render_width, render_height }); std::transform(low_res_heights.begin(), low_res_heights.end(), high_res_heights.begin(), height_diffs.begin(), [](auto a, auto b) { return std::abs(a - b); }); image::debugOut(height_diffs, "./ov_with_warping_diff_low_res_high_res.png"); } @@ -299,7 +300,7 @@ TEST_CASE("reading") const auto t = std::abs(double(a) - double(b)); largest_abs_diff = std::max(t, largest_abs_diff); return t * t; - }) / double(low_res_heights.size()); + }) / double(low_res_heights.buffer_length()); // fmt::print("mse: {}, largest_abs_diff: {}\n", mse, largest_abs_diff); CHECK(largest_abs_diff < double(max_abs_diff)); CHECK(mse < max_mse); @@ -332,7 +333,7 @@ TEST_CASE("reading") image::debugOut(low_res_heights, fmt::format("./lowres_ov_with_warping_low_res_heights.png")); image::debugOut(high_res_heights, fmt::format("./lowres_ov_with_warping_high_res_heights.png")); - auto height_diffs = HeightData(render_width, render_height); + auto height_diffs = radix::Raster({ render_width, render_height }); std::transform(low_res_heights.begin(), low_res_heights.end(), high_res_heights.begin(), height_diffs.begin(), [](auto a, auto b) { return std::abs(a - b); }); image::debugOut(height_diffs, "./lowres_ov_with_warping_diff_low_res_high_res.png"); } @@ -341,7 +342,7 @@ TEST_CASE("reading") const auto t = std::abs(double(a) - double(b)); largest_abs_diff = std::max(t, largest_abs_diff); return t * t; - }) / double(low_res_heights.size()); + }) / double(low_res_heights.buffer_length()); // fmt::print("render w/h: {}/{}, mse: {}, largest_abs_diff: {}\n", render_width, render_height, mse, largest_abs_diff); CHECK(largest_abs_diff < double(max_abs_diff)); CHECK(mse < max_mse); diff --git a/unittests/tilebuilder/image.cpp b/unittests/tilebuilder/image.cpp deleted file mode 100644 index 448aef27..00000000 --- a/unittests/tilebuilder/image.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/***************************************************************************** - * Alpine Terrain Builder - * Copyright (C) 2022 alpinemaps.org - * Copyright (C) 2022 Adam Celarek - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - *****************************************************************************/ - -#include -#include -#include -#include "Image.h" - -using Catch::Approx; - -TEST_CASE("image") -{ - SECTION("iteration") - { - HeightData d { 40, 60 }; - REQUIRE(d.size() == 40 * 60); - REQUIRE(d.height() == 60); - REQUIRE(d.width() == 40); - int v = 0; - std::ranges::for_each(d, [&](auto& d) { d = float(v++); }); - - v = 0; - for (unsigned r = 0; r < d.height(); ++r) { - for (unsigned c = 0; c < d.width(); ++c) { - REQUIRE(d.pixel(r, c) == Approx(float(v++))); - } - } - } - - SECTION("conversion") - { - HeightData d { 40, 60 }; - int v_init = 0; - std::ranges::for_each(d, [&](auto& d) { d = float(v_init++); }); - - Image image = image::transformImage(d, [&](auto v) { const auto b = uchar(255.F * v / float(v_init)); return glm::u8vec3(b, b, b); }); - const auto max = float(v_init); - v_init = 0; - for (unsigned r = 0; r < d.height(); ++r) { - for (unsigned c = 0; c < d.width(); ++c) { - const auto t = uchar(float(v_init) * 255.F / max); - REQUIRE(image.pixel(r, c).x == t); - REQUIRE(image.pixel(r, c).y == t); - REQUIRE(image.pixel(r, c).z == t); - v_init++; - } - } -// image::saveImageAsPng(image, "/home/madam/Documents/work/tuw/alpinemaps/tmp/test.png"); - } -} diff --git a/unittests/tilebuilder/image_writer.cpp b/unittests/tilebuilder/image_writer.cpp new file mode 100644 index 00000000..aeddf2f2 --- /dev/null +++ b/unittests/tilebuilder/image_writer.cpp @@ -0,0 +1,86 @@ +/***************************************************************************** + * Alpine Terrain Builder + * Copyright (C) 2022 alpinemaps.org + * Copyright (C) 2022 Adam Celarek + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *****************************************************************************/ + +#include +#include +#include + +#include +#include +#include + +#include "image_writer.h" + +TEST_CASE("tile builder writes radix rasters as PNG images") +{ + const auto output_path = std::filesystem::temp_directory_path() / "alpine_terrain_builder_image_writer_test.png"; + std::filesystem::remove(output_path); + + radix::Raster raster({ 2, 2 }); + raster.pixel({ 0, 0 }) = { 255, 0, 0 }; + raster.pixel({ 1, 0 }) = { 0, 255, 0 }; + raster.pixel({ 0, 1 }) = { 0, 0, 255 }; + raster.pixel({ 1, 1 }) = { 255, 255, 255 }; + + image::saveImageAsPng(raster, output_path.string()); + + const auto decoded = cv::imread(output_path.string(), cv::IMREAD_COLOR); + REQUIRE(decoded.rows == 2); + REQUIRE(decoded.cols == 2); + CHECK(decoded.at(0, 0) == cv::Vec3b(255, 0, 0)); + CHECK(decoded.at(0, 1) == cv::Vec3b(255, 255, 255)); + CHECK(decoded.at(1, 0) == cv::Vec3b(0, 0, 255)); + CHECK(decoded.at(1, 1) == cv::Vec3b(0, 255, 0)); + + std::filesystem::remove(output_path); +} + +TEST_CASE("tile builder handles image writer edge cases") +{ + SECTION("empty debug raster is rejected") + { + CHECK_THROWS_AS(image::debugOut(radix::Raster {}, "empty.png"), std::invalid_argument); + } + + SECTION("constant debug raster is written as black") + { + const auto output_path = std::filesystem::temp_directory_path() / "alpine_terrain_builder_constant_raster_test.png"; + std::filesystem::remove(output_path); + + radix::Raster raster({ 2, 2 }); + std::ranges::fill(raster, 42.F); + image::debugOut(raster, output_path.string()); + + const auto decoded = cv::imread(output_path.string(), cv::IMREAD_COLOR); + REQUIRE(decoded.rows == 2); + REQUIRE(decoded.cols == 2); + CHECK(cv::countNonZero(decoded.reshape(1)) == 0); + + std::filesystem::remove(output_path); + } + + SECTION("PNG write failure is reported") + { + const auto missing_directory = std::filesystem::temp_directory_path() / "alpine_terrain_builder_missing_directory"; + std::filesystem::remove_all(missing_directory); + const auto output_path = missing_directory / "image.png"; + + CHECK_THROWS_AS(image::saveImageAsPng(radix::Raster({ 1, 1 }), output_path.string()), std::runtime_error); + } +} diff --git a/unittests/tilebuilder/parallel_tile_generator.cpp b/unittests/tilebuilder/parallel_tile_generator.cpp index ee57262d..3fb7a7c4 100644 --- a/unittests/tilebuilder/parallel_tile_generator.cpp +++ b/unittests/tilebuilder/parallel_tile_generator.cpp @@ -43,7 +43,7 @@ TEST_CASE("parallel tile generator") , m_validation_error_counter(validation_error_counter) { } - void write(const std::string& file_path, const radix::tile::Descriptor& tile, const HeightData& heights) const override + void write(const std::string& file_path, const radix::tile::Descriptor& tile, const radix::Raster& heights) const override { if (file_path.empty() || tile.gridSize != 256 || heights.width() != 256 || heights.height() != 256) (*m_validation_error_counter)++;