From 703464aa1626d306fa08ec07711a64954905fd7d Mon Sep 17 00:00:00 2001 From: Tymofii Baga <3448+fester@users.noreply.github.com> Date: Tue, 15 Jan 2019 12:46:17 +0100 Subject: [PATCH 01/13] Compute vertex normals in Mesh. Signed-off-by: Tymofii Baga <3448+fester@users.noreply.github.com> --- include/tntn/Mesh.h | 8 +++++-- include/tntn/geometrix.h | 2 ++ src/Mesh.cpp | 45 +++++++++++++++++++++++++++++++++++++++- test/src/Mesh_tests.cpp | 29 ++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 3 deletions(-) diff --git a/include/tntn/Mesh.h b/include/tntn/Mesh.h index 89e77dd..47febbb 100644 --- a/include/tntn/Mesh.h +++ b/include/tntn/Mesh.h @@ -44,7 +44,8 @@ class Mesh SimpleRange triangles() const; SimpleRange faces() const; SimpleRange vertices() const; - + SimpleRange vertex_normals() const; + void grab_triangles(std::vector& into); void grab_decomposed(std::vector& vertices, std::vector& faces); @@ -62,6 +63,8 @@ class Mesh bool is_square() const; bool check_for_holes_in_square_mesh() const; + void compute_vertex_normals(); + private: bool semantic_equal_tri_tri(const Mesh& other) const; bool semantic_equal_dec_dec(const Mesh& other) const; @@ -70,7 +73,8 @@ class Mesh std::vector m_vertices; std::vector m_faces; std::vector m_triangles; - + std::vector m_normals; + void decompose_triangle(const Triangle& t); }; diff --git a/include/tntn/geometrix.h b/include/tntn/geometrix.h index 48322a9..6183fd6 100644 --- a/include/tntn/geometrix.h +++ b/include/tntn/geometrix.h @@ -18,6 +18,8 @@ typedef std::array Triangle; typedef size_t VertexIndex; //0-based index into an array/vector of vertices typedef std::array Face; +typedef glm::dvec3 Normal; + struct Edge { Edge() = default; diff --git a/src/Mesh.cpp b/src/Mesh.cpp index fe478f5..8526387 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -5,11 +5,11 @@ #include #include #include +#include #include "tntn/tntn_assert.h" #include "tntn/logging.h" #include "tntn/geometrix.h" - namespace tntn { void Mesh::clear() @@ -217,6 +217,15 @@ SimpleRange Mesh::vertices() const return {m_vertices.data(), m_vertices.data() + m_vertices.size()}; } +SimpleRange Mesh::vertex_normals() const +{ + if(m_normals.empty()) + { + return {nullptr, nullptr}; + } + return {m_normals.data(), m_normals.data() + m_normals.size()}; +} + void Mesh::grab_triangles(std::vector& into) { into.clear(); @@ -710,4 +719,38 @@ bool Mesh::check_tin_properties() const return true; } +void Mesh::compute_vertex_normals() +{ + using namespace glm; + + std::vector face_normals; + face_normals.reserve(m_faces.size()); + m_normals.resize(m_vertices.size()); + + auto face_normal = [](const Triangle& t) { + return normalize(cross(t[0] - t[2], t[1] - t[2])); + }; + + std::transform(m_triangles.begin(), m_triangles.end(), face_normals.begin(), face_normal); + + const int faces_count = m_faces.size(); + + for(int f = 0; f < faces_count; f++) + { + const auto& face = m_faces[f]; + const auto& face_normal = face_normals[f]; + + for(int v = 0; v < 3; v++) + { + const int vertex_id = face[v]; + m_normals[vertex_id] += face_normal; + } + } + + for(auto& vertex_normal : m_normals) + { + vertex_normal = normalize(vertex_normal); + } +} + } //namespace tntn diff --git a/test/src/Mesh_tests.cpp b/test/src/Mesh_tests.cpp index 2da3c01..eb8e3e6 100644 --- a/test/src/Mesh_tests.cpp +++ b/test/src/Mesh_tests.cpp @@ -322,5 +322,34 @@ TEST_CASE("Mesh::check_tin_properties detects unreferenced vertices") CHECK(!m.check_tin_properties()); } +TEST_CASE("Mesh::compute_normals normals") +{ + Mesh m; + + m.from_decomposed( + { + {0, 0, 1}, + {1, 0, 2}, + {1, 1, 3}, + {0, 1, 4}, + {0.5, -1, 5}, + }, + { + {{0, 1, 2}}, //ccw + {{0, 2, 3}}, //ccw + {{0, 4, 1}}, //ccw + }); + m.generate_triangles(); + m.compute_vertex_normals(); + + const auto vnormals = m.vertex_normals(); + CHECK(std::distance(vnormals.begin, vnormals.end) == 5); + + for(auto n = vnormals.begin; n != vnormals.end; ++n) + { + CHECK((glm::length(*n) - 1.0) < 0.00001); + } +} + } //namespace unittests } //namespace tntn From 9bf2414b36d5027d870d32f38e697e530541331b Mon Sep 17 00:00:00 2001 From: Tymofii Baga <3448+fester@users.noreply.github.com> Date: Fri, 25 Jan 2019 11:09:52 +0100 Subject: [PATCH 02/13] Added has_normals() to Mesh. Signed-off-by: Tymofii Baga <3448+fester@users.noreply.github.com> --- include/tntn/Mesh.h | 1 + src/Mesh.cpp | 13 +++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/include/tntn/Mesh.h b/include/tntn/Mesh.h index 47febbb..8b08324 100644 --- a/include/tntn/Mesh.h +++ b/include/tntn/Mesh.h @@ -64,6 +64,7 @@ class Mesh bool check_for_holes_in_square_mesh() const; void compute_vertex_normals(); + bool has_normals() const; private: bool semantic_equal_tri_tri(const Mesh& other) const; diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 8526387..7c64a0e 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -17,6 +17,7 @@ void Mesh::clear() m_triangles.clear(); m_vertices.clear(); m_faces.clear(); + m_normals.clear(); } void Mesh::clear_triangles() @@ -36,6 +37,7 @@ Mesh Mesh::clone() const out.m_faces = m_faces; out.m_vertices = m_vertices; out.m_triangles = m_triangles; + out.m_normals = m_normals; return out; } @@ -719,6 +721,11 @@ bool Mesh::check_tin_properties() const return true; } +bool Mesh::has_normals() const +{ + return !m_normals.empty(); +} + void Mesh::compute_vertex_normals() { using namespace glm; @@ -747,10 +754,8 @@ void Mesh::compute_vertex_normals() } } - for(auto& vertex_normal : m_normals) - { - vertex_normal = normalize(vertex_normal); - } + std::transform(m_normals.begin(), m_normals.end(), m_normals.begin(), + [](const auto &n) { return normalize(n); }); } } //namespace tntn From cc2b9b7afef17320e5bb5dc2592cdc126bbe5086 Mon Sep 17 00:00:00 2001 From: Tymofii Baga <3448+fester@users.noreply.github.com> Date: Fri, 25 Jan 2019 11:12:21 +0100 Subject: [PATCH 03/13] QuantizedMeshIO can write mesh normals. Signed-off-by: Tymofii Baga <3448+fester@users.noreply.github.com> --- src/QuantizedMeshIO.cpp | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/QuantizedMeshIO.cpp b/src/QuantizedMeshIO.cpp index a865878..881e1da 100644 --- a/src/QuantizedMeshIO.cpp +++ b/src/QuantizedMeshIO.cpp @@ -91,6 +91,12 @@ struct QuantizedMeshHeader // double HorizonOcclusionPointZ; }; +enum QuantizedMeshExtensionList +{ + QMExtensionNormals = 1, + QMExtensionWaterMask = 2 +}; + namespace detail { uint16_t zig_zag_encode(int16_t i) @@ -317,6 +323,37 @@ bool point_to_ecef(Vertex& p) return (bool)tr->Transform(1, &p.x, &p.y, &p.z); } +void oct_encode(const Normal& v, uint8_t out[2]) +{ + const glm::dvec2 p = v.xy * (1.0 / (abs(v.x) + abs(v.y) + abs(v.z))); + const auto signNotZero = glm::dvec2(v.x >= 0 ? 1.0 : -1.0, v.y > 0 ? 1.0 : -1.0); + const glm::dvec2 abs_v = p.yx; + glm::dvec2 ov = (v.z <= 0.0) ? ((1.0 - glm::abs(abs_v)) * signNotZero) : p; + + ov = (ov + 1.0) / 2.0; + + out[0] = static_cast(ov.x * 255); + out[1] = static_cast(ov.y * 255); +} + +void write_normals(BinaryIO& bio, + BinaryIOErrorTracker& e, + const SimpleRange& normals) +{ + // Write extension header + bio.write_byte(QMExtensionNormals, e); + bio.write_uint32(normals.distance() * 2, e); + + uint8_t oct_normal[2]; + + for(auto n = normals.begin; n != normals.end; n++) + { + oct_encode(*n, oct_normal); + bio.write_byte(oct_normal[0], e); + bio.write_byte(oct_normal[1], e); + } +} + bool write_mesh_as_qm(const std::shared_ptr& f, const Mesh& m, const BBox3D& bbox, @@ -496,6 +533,11 @@ bool write_mesh_as_qm(const std::shared_ptr& f, write_indices(bio, e, northlings); } + if(m.has_normals()) + { + write_normals(bio, e, m.vertex_normals()); + } + if(e.has_error()) { TNTN_LOG_ERROR("{} in file {}", e.to_string(), f->name()); @@ -503,6 +545,7 @@ bool write_mesh_as_qm(const std::shared_ptr& f, } TNTN_LOG_INFO("writer log: {}", log.to_string()); + return true; } From 8b85cedcc8733ff2460ffda121c6ce386e279488 Mon Sep 17 00:00:00 2001 From: Tymofii Baga <3448+fester@users.noreply.github.com> Date: Fri, 25 Jan 2019 17:54:42 +0100 Subject: [PATCH 04/13] Addded a command line switch to write QM normals. Signed-off-by: Tymofii Baga <3448+fester@users.noreply.github.com> --- include/tntn/TileMaker.h | 7 +++++-- include/tntn/dem2tintiles_workflow.h | 1 + src/TileMaker.cpp | 16 ++++++++++++---- src/cmd.cpp | 4 ++++ src/dem2tintiles_workflow.cpp | 3 ++- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/include/tntn/TileMaker.h b/include/tntn/TileMaker.h index f007c2c..2a6635a 100644 --- a/include/tntn/TileMaker.h +++ b/include/tntn/TileMaker.h @@ -10,10 +10,13 @@ namespace tntn { class TileMaker { std::unique_ptr m_mesh; - + bool m_normals_enabled; + public: - TileMaker() : m_mesh(std::make_unique()) {} + TileMaker(bool normals_enabled=false) : m_mesh(std::make_unique()), + m_normals_enabled(normals_enabled) {} + bool normals_enabled() const; void setMeshWriter(MeshWriter* w); bool loadObj(const char* filename); void loadMesh(std::unique_ptr mesh); diff --git a/include/tntn/dem2tintiles_workflow.h b/include/tntn/dem2tintiles_workflow.h index b50f3c9..cb1dc93 100644 --- a/include/tntn/dem2tintiles_workflow.h +++ b/include/tntn/dem2tintiles_workflow.h @@ -21,6 +21,7 @@ std::vector create_partitions_for_zoom_level(const RasterDouble& dem, bool create_tiles_for_zoom_level(const RasterDouble& dem, const std::vector& partitions, int zoom, + bool include_normals, const std::string& output_basedir, const double method_parameter, const std::string& meshing_method, diff --git a/src/TileMaker.cpp b/src/TileMaker.cpp index 8615d1a..5e26961 100644 --- a/src/TileMaker.cpp +++ b/src/TileMaker.cpp @@ -41,6 +41,11 @@ void TileMaker::loadMesh(std::unique_ptr mesh) m_mesh = std::move(mesh); } +bool TileMaker::normals_enabled() const +{ + return m_normals_enabled; +} + // Dump a tile into an terrain tile in format determined by a MeshWriter bool TileMaker::dumpTile(int tx, int ty, int zoom, const char* filename, MeshWriter& mesh_writer) { @@ -82,10 +87,8 @@ bool TileMaker::dumpTile(int tx, int ty, int zoom, const char* filename, MeshWri { for(int i = 0; i < 3; i++) { - if(triangle[i].z < tileSpaceBbox.min.z) - tileSpaceBbox.min.z = triangle[i].z; - if(triangle[i].z > tileSpaceBbox.max.z) - tileSpaceBbox.max.z = triangle[i].z; + if(triangle[i].z < tileSpaceBbox.min.z) tileSpaceBbox.min.z = triangle[i].z; + if(triangle[i].z > tileSpaceBbox.max.z) tileSpaceBbox.max.z = triangle[i].z; } } @@ -120,6 +123,11 @@ bool TileMaker::dumpTile(int tx, int ty, int zoom, const char* filename, MeshWri tileMesh.from_triangles(std::move(trianglesInTile)); tileMesh.generate_decomposed(); + if(normals_enabled()) + { + tileMesh.compute_vertex_normals(); + } + return mesh_writer.write_mesh_to_file(filename, tileMesh, tileSpaceBbox); } diff --git a/src/cmd.cpp b/src/cmd.cpp index c303e12..d2f917a 100644 --- a/src/cmd.cpp +++ b/src/cmd.cpp @@ -55,6 +55,7 @@ static int subcommand_dem2tintiles(bool need_help, ("max-error", po::value(), "max error parameter when using terra or zemlya method") ("step", po::value()->default_value(1), "grid spacing in pixels when using dense method") ("output-format", po::value()->default_value("terrain"), "output tiles in terrain (quantized mesh) or obj") + ("normals", po::bool_switch()->default_value(false), "generate normals for terrain") #if defined(TNTN_USE_ADDONS) && TNTN_USE_ADDONS ("method", po::value()->default_value("terra"), "meshing algorithm. one of: terra, zemlya, curvature or dense") ("threshold", po::value(), "threshold when using curvature method"); @@ -176,6 +177,8 @@ static int subcommand_dem2tintiles(bool need_help, throw po::error(std::string("unknown method ") + meshing_method); } + bool write_normals = local_varmap["normals"].as(); + RasterOverviews overviews(std::move(input_raster), min_zoom, max_zoom); RasterOverview overview; @@ -214,6 +217,7 @@ static int subcommand_dem2tintiles(bool need_help, if(!create_tiles_for_zoom_level(*overview.raster, partitions, zoom_level, + write_normals, output_basedir, max_error, meshing_method, diff --git a/src/dem2tintiles_workflow.cpp b/src/dem2tintiles_workflow.cpp index 4310145..d9a80b5 100644 --- a/src/dem2tintiles_workflow.cpp +++ b/src/dem2tintiles_workflow.cpp @@ -66,6 +66,7 @@ std::vector create_partitions_for_zoom_level(const RasterDouble& dem, bool create_tiles_for_zoom_level(const RasterDouble& dem, const std::vector& partitions, int zoom, + bool include_normals, const std::string& output_basedir, const double method_parameter, const std::string& meshing_method, @@ -128,7 +129,7 @@ bool create_tiles_for_zoom_level(const RasterDouble& dem, } // Cut the TIN into tiles - TileMaker tm; + TileMaker tm(include_normals); tm.loadMesh(std::move(mesh)); fs::create_directory(fs::path(output_basedir)); From 002aeee956f5c8e9d45df4f02d82fcc4b8f054bb Mon Sep 17 00:00:00 2001 From: Tymofii Baga <3448+fester@users.noreply.github.com> Date: Mon, 28 Jan 2019 10:49:31 +0100 Subject: [PATCH 05/13] Fix formatting. Signed-off-by: Tymofii Baga <3448+fester@users.noreply.github.com> --- include/tntn/Mesh.h | 14 +++++++------- include/tntn/TileMaker.h | 2 +- include/tntn/dem2tintiles_workflow.h | 2 +- src/Mesh.cpp | 11 ++++++----- src/cmd.cpp | 6 +++--- src/dem2tintiles_workflow.cpp | 2 +- 6 files changed, 19 insertions(+), 18 deletions(-) diff --git a/include/tntn/Mesh.h b/include/tntn/Mesh.h index 8b08324..8f961d4 100644 --- a/include/tntn/Mesh.h +++ b/include/tntn/Mesh.h @@ -44,8 +44,8 @@ class Mesh SimpleRange triangles() const; SimpleRange faces() const; SimpleRange vertices() const; - SimpleRange vertex_normals() const; - + SimpleRange vertex_normals() const; + void grab_triangles(std::vector& into); void grab_decomposed(std::vector& vertices, std::vector& faces); @@ -63,9 +63,9 @@ class Mesh bool is_square() const; bool check_for_holes_in_square_mesh() const; - void compute_vertex_normals(); - bool has_normals() const; - + void compute_vertex_normals(); + bool has_normals() const; + private: bool semantic_equal_tri_tri(const Mesh& other) const; bool semantic_equal_dec_dec(const Mesh& other) const; @@ -74,8 +74,8 @@ class Mesh std::vector m_vertices; std::vector m_faces; std::vector m_triangles; - std::vector m_normals; - + std::vector m_normals; + void decompose_triangle(const Triangle& t); }; diff --git a/include/tntn/TileMaker.h b/include/tntn/TileMaker.h index 2a6635a..20e0ec5 100644 --- a/include/tntn/TileMaker.h +++ b/include/tntn/TileMaker.h @@ -16,7 +16,7 @@ class TileMaker TileMaker(bool normals_enabled=false) : m_mesh(std::make_unique()), m_normals_enabled(normals_enabled) {} - bool normals_enabled() const; + bool normals_enabled() const; void setMeshWriter(MeshWriter* w); bool loadObj(const char* filename); void loadMesh(std::unique_ptr mesh); diff --git a/include/tntn/dem2tintiles_workflow.h b/include/tntn/dem2tintiles_workflow.h index cb1dc93..88eb0de 100644 --- a/include/tntn/dem2tintiles_workflow.h +++ b/include/tntn/dem2tintiles_workflow.h @@ -21,7 +21,7 @@ std::vector create_partitions_for_zoom_level(const RasterDouble& dem, bool create_tiles_for_zoom_level(const RasterDouble& dem, const std::vector& partitions, int zoom, - bool include_normals, + bool include_normals, const std::string& output_basedir, const double method_parameter, const std::string& meshing_method, diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 7c64a0e..f517165 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -17,7 +17,7 @@ void Mesh::clear() m_triangles.clear(); m_vertices.clear(); m_faces.clear(); - m_normals.clear(); + m_normals.clear(); } void Mesh::clear_triangles() @@ -37,7 +37,7 @@ Mesh Mesh::clone() const out.m_faces = m_faces; out.m_vertices = m_vertices; out.m_triangles = m_triangles; - out.m_normals = m_normals; + out.m_normals = m_normals; return out; } @@ -723,7 +723,7 @@ bool Mesh::check_tin_properties() const bool Mesh::has_normals() const { - return !m_normals.empty(); + return !m_normals.empty(); } void Mesh::compute_vertex_normals() @@ -754,8 +754,9 @@ void Mesh::compute_vertex_normals() } } - std::transform(m_normals.begin(), m_normals.end(), m_normals.begin(), - [](const auto &n) { return normalize(n); }); + std::transform(m_normals.begin(), m_normals.end(), m_normals.begin(), [](const auto& n) { + return normalize(n); + }); } } //namespace tntn diff --git a/src/cmd.cpp b/src/cmd.cpp index d2f917a..ce112e4 100644 --- a/src/cmd.cpp +++ b/src/cmd.cpp @@ -177,8 +177,8 @@ static int subcommand_dem2tintiles(bool need_help, throw po::error(std::string("unknown method ") + meshing_method); } - bool write_normals = local_varmap["normals"].as(); - + bool write_normals = local_varmap["normals"].as(); + RasterOverviews overviews(std::move(input_raster), min_zoom, max_zoom); RasterOverview overview; @@ -217,7 +217,7 @@ static int subcommand_dem2tintiles(bool need_help, if(!create_tiles_for_zoom_level(*overview.raster, partitions, zoom_level, - write_normals, + write_normals, output_basedir, max_error, meshing_method, diff --git a/src/dem2tintiles_workflow.cpp b/src/dem2tintiles_workflow.cpp index d9a80b5..77ea52a 100644 --- a/src/dem2tintiles_workflow.cpp +++ b/src/dem2tintiles_workflow.cpp @@ -66,7 +66,7 @@ std::vector create_partitions_for_zoom_level(const RasterDouble& dem, bool create_tiles_for_zoom_level(const RasterDouble& dem, const std::vector& partitions, int zoom, - bool include_normals, + bool include_normals, const std::string& output_basedir, const double method_parameter, const std::string& meshing_method, From 1d965aa78f430d521506b308a59757fcdd4d6a95 Mon Sep 17 00:00:00 2001 From: Tymofii Baga <3448+fester@users.noreply.github.com> Date: Mon, 4 Feb 2019 18:37:30 +0100 Subject: [PATCH 06/13] Correctly limit zoom levels for raster overviews. Signed-off-by: Tymofii Baga <3448+fester@users.noreply.github.com> --- src/RasterOverviews.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/RasterOverviews.cpp b/src/RasterOverviews.cpp index 5d07c71..1be8f5f 100644 --- a/src/RasterOverviews.cpp +++ b/src/RasterOverviews.cpp @@ -60,12 +60,18 @@ void RasterOverviews::compute_zoom_levels() m_estimated_min_zoom = guess_min_zoom_level(m_estimated_max_zoom); m_min_zoom = std::max(m_min_zoom, m_estimated_min_zoom); - m_max_zoom = std::min(std::max(0, m_max_zoom), m_estimated_max_zoom); + + if(m_max_zoom < 0 || m_max_zoom > m_estimated_max_zoom) + { + m_max_zoom = m_estimated_max_zoom; + } if(m_max_zoom < m_min_zoom) { std::swap(m_min_zoom, m_max_zoom); } + + TNTN_LOG_INFO("After checking with data, tiles will be generated in a range between {} and {}", m_min_zoom, m_max_zoom); } bool RasterOverviews::next(RasterOverview& overview) From c9a11ecd5de21feab944cdec3bf9413a1d0ffd29 Mon Sep 17 00:00:00 2001 From: Tymofii Baga <3448+fester@users.noreply.github.com> Date: Tue, 5 Feb 2019 13:12:43 +0100 Subject: [PATCH 07/13] Formatting gone wrong, again. Signed-off-by: Tymofii Baga <3448+fester@users.noreply.github.com> --- src/RasterOverviews.cpp | 43 ++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/RasterOverviews.cpp b/src/RasterOverviews.cpp index 1be8f5f..ccc0bbf 100644 --- a/src/RasterOverviews.cpp +++ b/src/RasterOverviews.cpp @@ -22,35 +22,34 @@ RasterOverviews::RasterOverviews(UniqueRasterPointer input_raster, int min_zoom, // Guesses (numerically) maximal zoom level from a raster resolution int RasterOverviews::guess_max_zoom_level(double resolution) { - // pixel_size_z0 is a magic number that is number of meters per pixel on zoom level 0, given tile size is 256 pixels. - // This number is approximate and does not account for latitude, i.e. uses pixel size on equator. - // The formula is: earth circumference * 2 * pi / 256 - // "Real" number can be off up to 30% depending on the latitude. More details here: https://msdn.microsoft.com/en-us/library/aa940990.aspx - const double pixel_size_z0 = 156543.04; + // pixel_size_z0 is a magic number that is number of meters per pixel on zoom level 0, given tile size is 256 pixels. + // This number is approximate and does not account for latitude, i.e. uses pixel size on equator. + // The formula is: earth circumference * 2 * pi / 256 + // "Real" number can be off up to 30% depending on the latitude. More details here: https://msdn.microsoft.com/en-us/library/aa940990.aspx + const double pixel_size_z0 = 156543.04; return static_cast(round(log2(pixel_size_z0 / resolution))); } // Guesses (numerically) minimal zoom level from a raster resolution and it's size int RasterOverviews::guess_min_zoom_level(int max_zoom_level) { - // This constant is an arbitrary number representing some minimal size to which the raster can be downsized when 'zooming out' a map. - // Math is simple: - // 2**max_zoom = raster_size; 2**min_zoom = 128 - // Solve it in regards to min_zoom and there you have it. - const int MINIMAL_RASTER_SIZE = 128; + // This constant is an arbitrary number representing some minimal size to which the raster can be downsized when 'zooming out' a map. + // Math is simple: + // 2**max_zoom = raster_size; 2**min_zoom = 128 + // Solve it in regards to min_zoom and there you have it. + const int MINIMAL_RASTER_SIZE = 128; const double quotient = MINIMAL_RASTER_SIZE * (1 << max_zoom_level); int raster_width = m_base_raster->get_width(); int raster_height = m_base_raster->get_height(); - TNTN_LOG_DEBUG("guess_min_zoom_level: raster_width: {}, raster_height: {}", - raster_width, - raster_height); + TNTN_LOG_DEBUG( + "guess_min_zoom_level: raster_width: {}, raster_height: {}", raster_width, raster_height); int zoom_x = static_cast(floor(log2(quotient / raster_width))); int zoom_y = static_cast(floor(log2(quotient / raster_height))); - // Cap the resulting value so it won't go below 0 and we wouldn't end up with negative zoom levels + // Cap the resulting value so it won't go below 0 and we wouldn't end up with negative zoom levels return std::max(0, std::min(zoom_x, zoom_y)); } @@ -71,7 +70,10 @@ void RasterOverviews::compute_zoom_levels() std::swap(m_min_zoom, m_max_zoom); } - TNTN_LOG_INFO("After checking with data, tiles will be generated in a range between {} and {}", m_min_zoom, m_max_zoom); + TNTN_LOG_INFO( + "After checking with data, tiles will be generated in a range between {} and {}", + m_min_zoom, + m_max_zoom); } bool RasterOverviews::next(RasterOverview& overview) @@ -94,11 +96,12 @@ bool RasterOverviews::next(RasterOverview& overview) overview.resolution = output_raster->get_cell_size(); overview.raster = std::move(output_raster); - TNTN_LOG_DEBUG("Generated next overview at zoom {}, window size {}, min zoom level {}, max zoom level {}", - m_current_zoom + 1, - window_size, - m_min_zoom, - m_max_zoom); + TNTN_LOG_DEBUG( + "Generated next overview at zoom {}, window size {}, min zoom level {}, max zoom level {}", + m_current_zoom + 1, + window_size, + m_min_zoom, + m_max_zoom); return true; } From a8cb8ef64353f1b4b76fc3d2abb07738867be2c5 Mon Sep 17 00:00:00 2001 From: Guido Schulz Date: Sun, 28 Apr 2019 13:25:25 +0200 Subject: [PATCH 08/13] Make windows build compatible with VS 2017 Signed-off-by: Guido Schulz --- CMakeLists.txt | 24 +++++++++++++++++------- include/tntn/File.h | 4 ++++ include/tntn/FileFormat.h | 5 +++++ include/tntn/endianness.h | 2 +- src/benchmark_workflow.cpp | 22 +++++++++++----------- src/dem2tintiles_workflow.cpp | 2 +- 6 files changed, 39 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bcbc5a..777e763 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,11 @@ option(TNTN_DOWNLOAD_DEPS "download dependencies during cmake configure" ON) set(CMAKE_CXX_STANDARD 14) +if(MSVC) + if(MSVC_VERSION LESS 1910) + message(FATAL_ERROR "Visual Studio 2017 version 15.0 or higher is required") + endif() +endif() # get the current working branch execute_process( @@ -31,14 +36,19 @@ find_package(GDAL REQUIRED) if(NOT GDAL_FOUND) message(FATAL_ERROR "GDAL not found, cannot proceed") endif() -if(NOT GDAL_CONFIG) - message(FATAL_ERROR "gdal-config command not found (not in PATH?), cannot proceed") -endif() -execute_process( - COMMAND ${GDAL_CONFIG} --version - OUTPUT_VARIABLE SYSTEM_GDAL_VERSION -) +if(GDAL_VERSION) + set(SYSTEM_GDAL_VERSION ${GDAL_VERSION}) +else() + if(NOT GDAL_CONFIG) + message(FATAL_ERROR "gdal-config command not found (not in PATH?), cannot proceed") + endif() + + execute_process( + COMMAND ${GDAL_CONFIG} --version + OUTPUT_VARIABLE SYSTEM_GDAL_VERSION + ) +endif() if(SYSTEM_GDAL_VERSION VERSION_LESS "2.2") message(FATAL_ERROR "GDAL version \"${SYSTEM_GDAL_VERSION}\" is too old, at least 2.2 is required") diff --git a/include/tntn/File.h b/include/tntn/File.h index 33151c8..b48a149 100644 --- a/include/tntn/File.h +++ b/include/tntn/File.h @@ -6,6 +6,10 @@ #include #include +#ifdef _MSC_VER +#define ftello ftell +#endif + namespace tntn { class FileLike diff --git a/include/tntn/FileFormat.h b/include/tntn/FileFormat.h index 6253585..3b5eea3 100644 --- a/include/tntn/FileFormat.h +++ b/include/tntn/FileFormat.h @@ -4,6 +4,11 @@ #include #include "tntn/MeshMode.h" +#ifdef _MSC_VER +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#endif + namespace tntn { class FileFormat diff --git a/include/tntn/endianness.h b/include/tntn/endianness.h index ab9c58a..8eadb14 100644 --- a/include/tntn/endianness.h +++ b/include/tntn/endianness.h @@ -8,7 +8,7 @@ # define TNTN_BIG_ENDIAN #elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || defined(__LITTLE_ENDIAN__) || \ defined(__ARMEL__) || defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || \ - defined(__MIPSEL) || defined(__MIPSEL__) + defined(__MIPSEL) || defined(__MIPSEL__) || defined(_WIN32) # define TNTN_LITTLE_ENDIAN #else # error unknown architecture diff --git a/src/benchmark_workflow.cpp b/src/benchmark_workflow.cpp index ae34870..7a5539f 100644 --- a/src/benchmark_workflow.cpp +++ b/src/benchmark_workflow.cpp @@ -158,7 +158,7 @@ static bool prepare_output_directory(const fs::path& output_dir, const bool resu if(e) { TNTN_LOG_ERROR("unable to create output directory {} with message: {}", - output_dir.c_str(), + output_dir.string().c_str(), e.message()); return false; } @@ -167,13 +167,13 @@ static bool prepare_output_directory(const fs::path& output_dir, const bool resu //make sure it is a directory if(!fs::is_directory(output_dir, e)) { - TNTN_LOG_ERROR("output directory {} is not a directory", output_dir.c_str()); + TNTN_LOG_ERROR("output directory {} is not a directory", output_dir.string().c_str()); return false; } if(e) { TNTN_LOG_ERROR("filesystem error while checking output directory {} with message: {}", - output_dir.c_str(), + output_dir.string().c_str(), e.message()); return false; } @@ -183,13 +183,13 @@ static bool prepare_output_directory(const fs::path& output_dir, const bool resu //make sure it is empty if(!fs::is_empty(output_dir, e)) { - TNTN_LOG_ERROR("output directory {} is not empty", output_dir.c_str()); + TNTN_LOG_ERROR("output directory {} is not empty", output_dir.string().c_str()); return false; } if(e) { TNTN_LOG_ERROR("filesystem error while checking output directory {} with message: {}", - output_dir.c_str(), + output_dir.string().c_str(), e.message()); return false; } @@ -662,13 +662,13 @@ static bool write_mesh_as_obj_and_off(const fs::path& parametrization_subdir, bool rc = true; File f; - if(f.open(obj_filename.c_str(), File::OM_RWC)) + if(f.open(obj_filename.string().c_str(), File::OM_RWC)) { rc = rc && write_mesh_as_obj(f, m); f.close(); } - if(f.open(off_filename.c_str(), File::OM_RWC)) + if(f.open(off_filename.string().c_str(), File::OM_RWC)) { rc = rc && write_mesh_as_off(f, m); f.close(); @@ -688,7 +688,7 @@ static bool write_raster_as_asc_with_prefix(const fs::path& parametrization_subd raster_filename = parametrization_subdir / raster_filename; File f; - if(!f.open(raster_filename.c_str(), File::OM_RWC)) + if(!f.open(raster_filename.string().c_str(), File::OM_RWC)) { return false; } @@ -941,7 +941,7 @@ static bool run_all_dem2tin_method_benchmarks_on_single_file( //create is_done_file to signal to later resume runs File f; - f.open(is_done_file.c_str(), File::OM_RWC); + f.open(is_done_file.string().c_str(), File::OM_RWC); } } return true; @@ -1006,7 +1006,7 @@ bool run_dem2tin_method_benchmarks(const std::string& output_dir, const auto benchmark_csv_filename = output_dir_p / "tin_terrain_benchmarks.csv"; if(resume && fs::exists(benchmark_csv_filename)) { - if(!csv_file->open(benchmark_csv_filename.c_str(), File::OM_RW)) + if(!csv_file->open(benchmark_csv_filename.string().c_str(), File::OM_RW)) { TNTN_LOG_ERROR("unable to open CSV output file {} for resume", benchmark_csv_filename.string()); @@ -1015,7 +1015,7 @@ bool run_dem2tin_method_benchmarks(const std::string& output_dir, } else { - if(!csv_file->open(benchmark_csv_filename.c_str(), File::OM_RWC)) + if(!csv_file->open(benchmark_csv_filename.string().c_str(), File::OM_RWC)) { TNTN_LOG_ERROR("unable to create CSV output file {} for writing", benchmark_csv_filename.string()); diff --git a/src/dem2tintiles_workflow.cpp b/src/dem2tintiles_workflow.cpp index 4310145..646a213 100644 --- a/src/dem2tintiles_workflow.cpp +++ b/src/dem2tintiles_workflow.cpp @@ -147,7 +147,7 @@ bool create_tiles_for_zoom_level(const RasterDouble& dem, auto file_path = tile_dir / (std::to_string(ty) + "." + mesh_writer.file_extension()); - if(!tm.dumpTile(tx, ty, zoom, file_path.c_str(), mesh_writer)) + if(!tm.dumpTile(tx, ty, zoom, file_path.string().c_str(), mesh_writer)) { TNTN_LOG_ERROR("error dumping tile z:{} x:{} y:{}", zoom, tx, ty); return false; From 08c03e5f8fd9c6963c48dc38ffa4e1fa27a0ce68 Mon Sep 17 00:00:00 2001 From: Alexey Dolinenko Date: Fri, 15 Nov 2019 13:58:18 +0300 Subject: [PATCH 09/13] add max-iterations for Terra mesh --- include/tntn/TerraMesh.h | 2 +- include/tntn/terra_meshing.h | 6 +++--- src/TerraMesh.cpp | 5 ++++- src/cmd.cpp | 3 ++- src/terra_meshing.cpp | 12 ++++++------ 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/include/tntn/TerraMesh.h b/include/tntn/TerraMesh.h index 819fe97..3210f85 100644 --- a/include/tntn/TerraMesh.h +++ b/include/tntn/TerraMesh.h @@ -29,7 +29,7 @@ class TerraMesh : public TerraBaseMesh const double no_data_value); public: - void greedy_insert(double max_error); + void greedy_insert(double max_error, int max_iterations); void scan_triangle(dt_ptr t) override; std::unique_ptr convert_to_mesh(); }; diff --git a/include/tntn/terra_meshing.h b/include/tntn/terra_meshing.h index bc4e983..c4b61b4 100644 --- a/include/tntn/terra_meshing.h +++ b/include/tntn/terra_meshing.h @@ -8,10 +8,10 @@ namespace tntn { -std::unique_ptr generate_tin_terra(std::unique_ptr raster, double max_error); +std::unique_ptr generate_tin_terra(std::unique_ptr raster, double max_error, int max_iterations=0); std::unique_ptr generate_tin_terra(std::unique_ptr surface_points, - double max_error); -std::unique_ptr generate_tin_terra(const SurfacePoints& surface_points, double max_error); + double max_error, int max_iterations=0); +std::unique_ptr generate_tin_terra(const SurfacePoints& surface_points, double max_error, int max_iterations=0); } //namespace tntn diff --git a/src/TerraMesh.cpp b/src/TerraMesh.cpp index c11a2bf..6645f3b 100644 --- a/src/TerraMesh.cpp +++ b/src/TerraMesh.cpp @@ -13,7 +13,7 @@ namespace tntn { namespace terra { -void TerraMesh::greedy_insert(double max_error) +void TerraMesh::greedy_insert(double max_error, int max_iterations) { m_max_error = max_error; m_counter = 0; @@ -56,12 +56,14 @@ void TerraMesh::greedy_insert(double max_error) t = t->getLink(); } + int iterations_count = 0; // Iterate until the error threshold is met while(!m_candidates.empty()) { Candidate candidate = m_candidates.grab_greatest(); if(candidate.importance < m_max_error) continue; + if(max_iterations && iterations_count >= max_iterations) continue; // Skip if the candidate is not the latest if(m_token.value(candidate.y, candidate.x) != candidate.token) continue; @@ -70,6 +72,7 @@ void TerraMesh::greedy_insert(double max_error) //TNTN_LOG_DEBUG("inserting point: ({}, {}, {})", candidate.x, candidate.y, candidate.z); this->insert(glm::dvec2(candidate.x, candidate.y), candidate.triangle); + iterations_count++; } TNTN_LOG_INFO("finished greedy insertion"); diff --git a/src/cmd.cpp b/src/cmd.cpp index c303e12..de04d2c 100644 --- a/src/cmd.cpp +++ b/src/cmd.cpp @@ -283,6 +283,7 @@ static int subcommand_dem2tin(bool need_help, ("output", po::value(), "output filename") ("output-format", po::value()->default_value("auto"), "output file format, can be any of: auto, obj, off, terrain (quantized mesh), json/geojson") ("max-error", po::value(), "max error parameter when using terra or zemlya method") + ("max-iterations", po::value()->default_value(0), "max add point iterations when using terra or zemlya method") ("step", po::value(), "grid spacing in pixels when using dense method") #if defined(TNTN_USE_ADDONS) && TNTN_USE_ADDONS ("threshold", po::value(), "threshold when using curvature method") @@ -370,7 +371,7 @@ static int subcommand_dem2tin(bool need_help, if("terra" == method) { TNTN_LOG_INFO("performing terra meshing..."); - mesh = generate_tin_terra(std::move(raster), max_error); + mesh = generate_tin_terra(std::move(raster), max_error, local_varmap["max-iterations"].as()); } else if("zemlya" == method) { diff --git a/src/terra_meshing.cpp b/src/terra_meshing.cpp index 18cdd4f..9d8f11d 100644 --- a/src/terra_meshing.cpp +++ b/src/terra_meshing.cpp @@ -5,34 +5,34 @@ namespace tntn { -std::unique_ptr generate_tin_terra(std::unique_ptr raster, double max_error) +std::unique_ptr generate_tin_terra(std::unique_ptr raster, double max_error, int max_iterations) { TNTN_ASSERT(raster != nullptr); terra::TerraMesh g; g.load_raster(std::move(raster)); - g.greedy_insert(max_error); + g.greedy_insert(max_error, max_iterations); return g.convert_to_mesh(); } std::unique_ptr generate_tin_terra(std::unique_ptr surface_points, - double max_error) + double max_error, int max_iterations) { auto raster = surface_points->to_raster(); surface_points.reset(); terra::TerraMesh g; g.load_raster(std::move(raster)); - g.greedy_insert(max_error); + g.greedy_insert(max_error, max_iterations); return g.convert_to_mesh(); } -std::unique_ptr generate_tin_terra(const SurfacePoints& surface_points, double max_error) +std::unique_ptr generate_tin_terra(const SurfacePoints& surface_points, double max_error, int max_iterations) { auto raster = surface_points.to_raster(); terra::TerraMesh g; g.load_raster(std::move(raster)); - g.greedy_insert(max_error); + g.greedy_insert(max_error, max_iterations); return g.convert_to_mesh(); } From 97c33eb8a9b20aa85cb86d2126785b75f6b39a17 Mon Sep 17 00:00:00 2001 From: Alexey Dolinenko Date: Fri, 15 Nov 2019 17:09:58 +0300 Subject: [PATCH 10/13] throw error if max-iterations set for non terra method --- src/cmd.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cmd.cpp b/src/cmd.cpp index de04d2c..d39d463 100644 --- a/src/cmd.cpp +++ b/src/cmd.cpp @@ -360,6 +360,11 @@ static int subcommand_dem2tin(bool need_help, const auto t_start = std::chrono::high_resolution_clock::now(); + if(method != "terra" && local_varmap.count("max-iterations")) + { + throw po::error("parameter --max-iterations implemented for terra method only"); + } + if(method == "terra" || method == "zemlya") { double max_error = raster->get_cell_size(); From 2cebb52bccf07717fe4600c4ab66891f162f24ed Mon Sep 17 00:00:00 2001 From: Julian Fell Date: Thu, 16 Jan 2020 12:47:16 +1000 Subject: [PATCH 11/13] default to triangle mesh mode when generating a single .terrain file Signed-off-by: Julian Fell --- include/tntn/FileFormat.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/tntn/FileFormat.h b/include/tntn/FileFormat.h index 6253585..dac429d 100644 --- a/include/tntn/FileFormat.h +++ b/include/tntn/FileFormat.h @@ -114,8 +114,8 @@ class FileFormat switch(m_value) { case OBJ: //fallthrough - case OFF: //fallthrough - case TERRAIN: return MeshMode::decomposed; + case OFF: return MeshMode::decomposed; + case TERRAIN: return MeshMode::triangles; default: return MeshMode::none; } } From 3d18e290ca5dec1d68856d0963388a2951092e76 Mon Sep 17 00:00:00 2001 From: qGYdXbY2 <47661341+qGYdXbY2@users.noreply.github.com> Date: Tue, 25 Feb 2020 07:06:23 +0000 Subject: [PATCH 12/13] fix out of bounds on to_average array - 17435580.tif --- src/raster_tools.cpp | 33 +++++---------------------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/src/raster_tools.cpp b/src/raster_tools.cpp index 547f886..dd8b80c 100644 --- a/src/raster_tools.cpp +++ b/src/raster_tools.cpp @@ -324,22 +324,6 @@ static double subsample_raster_3x3(const RasterDouble& src, static constexpr int MAX_AVERAGING_SAMPLES = 64; -static inline double average(const std::array& to_average, - const int avg_count) -{ - if(avg_count == 0) - { - return NAN; - } - double sum = 0; - for(int i = 0; i < MAX_AVERAGING_SAMPLES; i++) - { - sum += to_average[i]; - } - const double avg = sum / avg_count; - return avg; -} - double raster_tools::sample_nearest_valid_avg(const RasterDouble& src, const unsigned int _row, const unsigned int _column, @@ -365,19 +349,18 @@ double raster_tools::sample_nearest_valid_avg(const RasterDouble& src, return z; } - std::array to_average; - std::fill(to_average.begin(), to_average.end(), 0.0); + double avg = 0.0; int avg_count = 0; - auto putpixel = [row, column, w, h, no_data_value, &src, &to_average, &avg_count](int x, + auto putpixel = [row, column, w, h, no_data_value, &src, &avg, &avg_count](int x, int y) { const int64_t dest_r = row + y; const int64_t dest_c = column + x; double z = subsample_raster_3x3(src, no_data_value, w, h, dest_r, dest_c); if(!is_no_data(z, no_data_value)) { - to_average[avg_count] = z; - avg_count++; + avg_count++; + avg = avg + ( z - avg ) / (double) avg_count; } }; @@ -417,13 +400,7 @@ double raster_tools::sample_nearest_valid_avg(const RasterDouble& src, } } - //short path for only one sample - if(avg_count == 1) - { - return to_average[0]; - } - - return average(to_average, avg_count); + return ( avg_count == 0 ? NAN : avg ); } } // namespace tntn From 42acac5099603ad9da3f44eee5959d51c102438a Mon Sep 17 00:00:00 2001 From: qGYdXbY2 <47661341+qGYdXbY2@users.noreply.github.com> Date: Tue, 25 Feb 2020 07:06:23 +0000 Subject: [PATCH 13/13] fix out of bounds on to_average array - 17435580.tif Signed-off-by: mnah --- src/raster_tools.cpp | 33 +++++---------------------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/src/raster_tools.cpp b/src/raster_tools.cpp index 547f886..dd8b80c 100644 --- a/src/raster_tools.cpp +++ b/src/raster_tools.cpp @@ -324,22 +324,6 @@ static double subsample_raster_3x3(const RasterDouble& src, static constexpr int MAX_AVERAGING_SAMPLES = 64; -static inline double average(const std::array& to_average, - const int avg_count) -{ - if(avg_count == 0) - { - return NAN; - } - double sum = 0; - for(int i = 0; i < MAX_AVERAGING_SAMPLES; i++) - { - sum += to_average[i]; - } - const double avg = sum / avg_count; - return avg; -} - double raster_tools::sample_nearest_valid_avg(const RasterDouble& src, const unsigned int _row, const unsigned int _column, @@ -365,19 +349,18 @@ double raster_tools::sample_nearest_valid_avg(const RasterDouble& src, return z; } - std::array to_average; - std::fill(to_average.begin(), to_average.end(), 0.0); + double avg = 0.0; int avg_count = 0; - auto putpixel = [row, column, w, h, no_data_value, &src, &to_average, &avg_count](int x, + auto putpixel = [row, column, w, h, no_data_value, &src, &avg, &avg_count](int x, int y) { const int64_t dest_r = row + y; const int64_t dest_c = column + x; double z = subsample_raster_3x3(src, no_data_value, w, h, dest_r, dest_c); if(!is_no_data(z, no_data_value)) { - to_average[avg_count] = z; - avg_count++; + avg_count++; + avg = avg + ( z - avg ) / (double) avg_count; } }; @@ -417,13 +400,7 @@ double raster_tools::sample_nearest_valid_avg(const RasterDouble& src, } } - //short path for only one sample - if(avg_count == 1) - { - return to_average[0]; - } - - return average(to_average, avg_count); + return ( avg_count == 0 ? NAN : avg ); } } // namespace tntn