Skip to content

Commit c962616

Browse files
committed
Terrain normals.fester heremaps#55
2 parents 74f1e54 + 91f1d7d commit c962616

11 files changed

+182
-28
lines changed

include/tntn/Mesh.h

+5
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class Mesh
4444
SimpleRange<const Triangle*> triangles() const;
4545
SimpleRange<const Face*> faces() const;
4646
SimpleRange<const Vertex*> vertices() const;
47+
SimpleRange<const Normal*> vertex_normals() const;
4748

4849
void grab_triangles(std::vector<Triangle>& into);
4950
void grab_decomposed(std::vector<Vertex>& vertices, std::vector<Face>& faces);
@@ -62,6 +63,9 @@ class Mesh
6263
bool is_square() const;
6364
bool check_for_holes_in_square_mesh() const;
6465

66+
void compute_vertex_normals();
67+
bool has_normals() const;
68+
6569
private:
6670
bool semantic_equal_tri_tri(const Mesh& other) const;
6771
bool semantic_equal_dec_dec(const Mesh& other) const;
@@ -70,6 +74,7 @@ class Mesh
7074
std::vector<Vertex> m_vertices;
7175
std::vector<Face> m_faces;
7276
std::vector<Triangle> m_triangles;
77+
std::vector<Normal> m_normals;
7378

7479
void decompose_triangle(const Triangle& t);
7580
};

include/tntn/TileMaker.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ namespace tntn {
1010
class TileMaker
1111
{
1212
std::unique_ptr<Mesh> m_mesh;
13-
13+
bool m_normals_enabled;
14+
1415
public:
15-
TileMaker() : m_mesh(std::make_unique<Mesh>()) {}
16+
TileMaker(bool normals_enabled=false) : m_mesh(std::make_unique<Mesh>()),
17+
m_normals_enabled(normals_enabled) {}
1618

19+
bool normals_enabled() const;
1720
void setMeshWriter(MeshWriter* w);
1821
bool loadObj(const char* filename);
1922
void loadMesh(std::unique_ptr<Mesh> mesh);

include/tntn/dem2tintiles_workflow.h

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ std::vector<Partition> create_partitions_for_zoom_level(const RasterDouble& dem,
2121
bool create_tiles_for_zoom_level(const RasterDouble& dem,
2222
const std::vector<Partition>& partitions,
2323
int zoom,
24+
bool include_normals,
2425
const std::string& output_basedir,
2526
const double method_parameter,
2627
const std::string& meshing_method,

include/tntn/geometrix.h

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ typedef std::array<Vertex, 3> Triangle;
1818
typedef size_t VertexIndex; //0-based index into an array/vector of vertices
1919
typedef std::array<VertexIndex, 3> Face;
2020

21+
typedef glm::dvec3 Normal;
22+
2123
struct Edge
2224
{
2325
Edge() = default;

src/Mesh.cpp

+50-1
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,19 @@
55
#include <limits>
66
#include <unordered_map>
77
#include <unordered_set>
8+
#include <glm/gtx/normal.hpp>
89

910
#include "tntn/tntn_assert.h"
1011
#include "tntn/logging.h"
1112
#include "tntn/geometrix.h"
12-
1313
namespace tntn {
1414

1515
void Mesh::clear()
1616
{
1717
m_triangles.clear();
1818
m_vertices.clear();
1919
m_faces.clear();
20+
m_normals.clear();
2021
}
2122

2223
void Mesh::clear_triangles()
@@ -36,6 +37,7 @@ Mesh Mesh::clone() const
3637
out.m_faces = m_faces;
3738
out.m_vertices = m_vertices;
3839
out.m_triangles = m_triangles;
40+
out.m_normals = m_normals;
3941
return out;
4042
}
4143

@@ -217,6 +219,15 @@ SimpleRange<const Vertex*> Mesh::vertices() const
217219
return {m_vertices.data(), m_vertices.data() + m_vertices.size()};
218220
}
219221

222+
SimpleRange<const Normal*> Mesh::vertex_normals() const
223+
{
224+
if(m_normals.empty())
225+
{
226+
return {nullptr, nullptr};
227+
}
228+
return {m_normals.data(), m_normals.data() + m_normals.size()};
229+
}
230+
220231
void Mesh::grab_triangles(std::vector<Triangle>& into)
221232
{
222233
into.clear();
@@ -710,4 +721,42 @@ bool Mesh::check_tin_properties() const
710721
return true;
711722
}
712723

724+
bool Mesh::has_normals() const
725+
{
726+
return !m_normals.empty();
727+
}
728+
729+
void Mesh::compute_vertex_normals()
730+
{
731+
using namespace glm;
732+
733+
std::vector<dvec3> face_normals;
734+
face_normals.reserve(m_faces.size());
735+
m_normals.resize(m_vertices.size());
736+
737+
auto face_normal = [](const Triangle& t) {
738+
return normalize(cross(t[0] - t[2], t[1] - t[2]));
739+
};
740+
741+
std::transform(m_triangles.begin(), m_triangles.end(), face_normals.begin(), face_normal);
742+
743+
const int faces_count = m_faces.size();
744+
745+
for(int f = 0; f < faces_count; f++)
746+
{
747+
const auto& face = m_faces[f];
748+
const auto& face_normal = face_normals[f];
749+
750+
for(int v = 0; v < 3; v++)
751+
{
752+
const int vertex_id = face[v];
753+
m_normals[vertex_id] += face_normal;
754+
}
755+
}
756+
757+
std::transform(m_normals.begin(), m_normals.end(), m_normals.begin(), [](const auto& n) {
758+
return normalize(n);
759+
});
760+
}
761+
713762
} //namespace tntn

src/QuantizedMeshIO.cpp

+43
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ struct QuantizedMeshHeader
9191
// double HorizonOcclusionPointZ;
9292
};
9393

94+
enum QuantizedMeshExtensionList
95+
{
96+
QMExtensionNormals = 1,
97+
QMExtensionWaterMask = 2
98+
};
99+
94100
namespace detail {
95101

96102
uint16_t zig_zag_encode(int16_t i)
@@ -317,6 +323,37 @@ bool point_to_ecef(Vertex& p)
317323
return (bool)tr->Transform(1, &p.x, &p.y, &p.z);
318324
}
319325

326+
void oct_encode(const Normal& v, uint8_t out[2])
327+
{
328+
const glm::dvec2 p = v.xy * (1.0 / (abs(v.x) + abs(v.y) + abs(v.z)));
329+
const auto signNotZero = glm::dvec2(v.x >= 0 ? 1.0 : -1.0, v.y > 0 ? 1.0 : -1.0);
330+
const glm::dvec2 abs_v = p.yx;
331+
glm::dvec2 ov = (v.z <= 0.0) ? ((1.0 - glm::abs(abs_v)) * signNotZero) : p;
332+
333+
ov = (ov + 1.0) / 2.0;
334+
335+
out[0] = static_cast<uint8_t>(ov.x * 255);
336+
out[1] = static_cast<uint8_t>(ov.y * 255);
337+
}
338+
339+
void write_normals(BinaryIO& bio,
340+
BinaryIOErrorTracker& e,
341+
const SimpleRange<const Normal*>& normals)
342+
{
343+
// Write extension header
344+
bio.write_byte(QMExtensionNormals, e);
345+
bio.write_uint32(normals.distance() * 2, e);
346+
347+
uint8_t oct_normal[2];
348+
349+
for(auto n = normals.begin; n != normals.end; n++)
350+
{
351+
oct_encode(*n, oct_normal);
352+
bio.write_byte(oct_normal[0], e);
353+
bio.write_byte(oct_normal[1], e);
354+
}
355+
}
356+
320357
bool write_mesh_as_qm(const std::shared_ptr<FileLike>& f,
321358
const Mesh& m,
322359
const BBox3D& bbox,
@@ -496,13 +533,19 @@ bool write_mesh_as_qm(const std::shared_ptr<FileLike>& f,
496533
write_indices<uint32_t>(bio, e, northlings);
497534
}
498535

536+
if(m.has_normals())
537+
{
538+
write_normals(bio, e, m.vertex_normals());
539+
}
540+
499541
if(e.has_error())
500542
{
501543
TNTN_LOG_ERROR("{} in file {}", e.to_string(), f->name());
502544
return false;
503545
}
504546

505547
TNTN_LOG_INFO("writer log: {}", log.to_string());
548+
506549
return true;
507550
}
508551

src/RasterOverviews.cpp

+29-20
Original file line numberDiff line numberDiff line change
@@ -22,35 +22,34 @@ RasterOverviews::RasterOverviews(UniqueRasterPointer input_raster, int min_zoom,
2222
// Guesses (numerically) maximal zoom level from a raster resolution
2323
int RasterOverviews::guess_max_zoom_level(double resolution)
2424
{
25-
// pixel_size_z0 is a magic number that is number of meters per pixel on zoom level 0, given tile size is 256 pixels.
26-
// This number is approximate and does not account for latitude, i.e. uses pixel size on equator.
27-
// The formula is: earth circumference * 2 * pi / 256
28-
// "Real" number can be off up to 30% depending on the latitude. More details here: https://msdn.microsoft.com/en-us/library/aa940990.aspx
29-
const double pixel_size_z0 = 156543.04;
25+
// pixel_size_z0 is a magic number that is number of meters per pixel on zoom level 0, given tile size is 256 pixels.
26+
// This number is approximate and does not account for latitude, i.e. uses pixel size on equator.
27+
// The formula is: earth circumference * 2 * pi / 256
28+
// "Real" number can be off up to 30% depending on the latitude. More details here: https://msdn.microsoft.com/en-us/library/aa940990.aspx
29+
const double pixel_size_z0 = 156543.04;
3030
return static_cast<int>(round(log2(pixel_size_z0 / resolution)));
3131
}
3232

3333
// Guesses (numerically) minimal zoom level from a raster resolution and it's size
3434
int RasterOverviews::guess_min_zoom_level(int max_zoom_level)
3535
{
36-
// This constant is an arbitrary number representing some minimal size to which the raster can be downsized when 'zooming out' a map.
37-
// Math is simple:
38-
// 2**max_zoom = raster_size; 2**min_zoom = 128
39-
// Solve it in regards to min_zoom and there you have it.
40-
const int MINIMAL_RASTER_SIZE = 128;
36+
// This constant is an arbitrary number representing some minimal size to which the raster can be downsized when 'zooming out' a map.
37+
// Math is simple:
38+
// 2**max_zoom = raster_size; 2**min_zoom = 128
39+
// Solve it in regards to min_zoom and there you have it.
40+
const int MINIMAL_RASTER_SIZE = 128;
4141
const double quotient = MINIMAL_RASTER_SIZE * (1 << max_zoom_level);
4242

4343
int raster_width = m_base_raster->get_width();
4444
int raster_height = m_base_raster->get_height();
4545

46-
TNTN_LOG_DEBUG("guess_min_zoom_level: raster_width: {}, raster_height: {}",
47-
raster_width,
48-
raster_height);
46+
TNTN_LOG_DEBUG(
47+
"guess_min_zoom_level: raster_width: {}, raster_height: {}", raster_width, raster_height);
4948

5049
int zoom_x = static_cast<int>(floor(log2(quotient / raster_width)));
5150
int zoom_y = static_cast<int>(floor(log2(quotient / raster_height)));
5251

53-
// Cap the resulting value so it won't go below 0 and we wouldn't end up with negative zoom levels
52+
// Cap the resulting value so it won't go below 0 and we wouldn't end up with negative zoom levels
5453
return std::max(0, std::min(zoom_x, zoom_y));
5554
}
5655

@@ -60,12 +59,21 @@ void RasterOverviews::compute_zoom_levels()
6059
m_estimated_min_zoom = guess_min_zoom_level(m_estimated_max_zoom);
6160

6261
m_min_zoom = std::max(m_min_zoom, m_estimated_min_zoom);
63-
m_max_zoom = std::min(std::max(0, m_max_zoom), m_estimated_max_zoom);
62+
63+
if(m_max_zoom < 0 || m_max_zoom > m_estimated_max_zoom)
64+
{
65+
m_max_zoom = m_estimated_max_zoom;
66+
}
6467

6568
if(m_max_zoom < m_min_zoom)
6669
{
6770
std::swap(m_min_zoom, m_max_zoom);
6871
}
72+
73+
TNTN_LOG_INFO(
74+
"After checking with data, tiles will be generated in a range between {} and {}",
75+
m_min_zoom,
76+
m_max_zoom);
6977
}
7078

7179
bool RasterOverviews::next(RasterOverview& overview)
@@ -88,11 +96,12 @@ bool RasterOverviews::next(RasterOverview& overview)
8896
overview.resolution = output_raster->get_cell_size();
8997
overview.raster = std::move(output_raster);
9098

91-
TNTN_LOG_DEBUG("Generated next overview at zoom {}, window size {}, min zoom level {}, max zoom level {}",
92-
m_current_zoom + 1,
93-
window_size,
94-
m_min_zoom,
95-
m_max_zoom);
99+
TNTN_LOG_DEBUG(
100+
"Generated next overview at zoom {}, window size {}, min zoom level {}, max zoom level {}",
101+
m_current_zoom + 1,
102+
window_size,
103+
m_min_zoom,
104+
m_max_zoom);
96105

97106
return true;
98107
}

src/TileMaker.cpp

+12-4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ void TileMaker::loadMesh(std::unique_ptr<Mesh> mesh)
4141
m_mesh = std::move(mesh);
4242
}
4343

44+
bool TileMaker::normals_enabled() const
45+
{
46+
return m_normals_enabled;
47+
}
48+
4449
// Dump a tile into an terrain tile in format determined by a MeshWriter
4550
bool TileMaker::dumpTile(int tx, int ty, int zoom, const char* filename, MeshWriter& mesh_writer)
4651
{
@@ -82,10 +87,8 @@ bool TileMaker::dumpTile(int tx, int ty, int zoom, const char* filename, MeshWri
8287
{
8388
for(int i = 0; i < 3; i++)
8489
{
85-
if(triangle[i].z < tileSpaceBbox.min.z)
86-
tileSpaceBbox.min.z = triangle[i].z;
87-
if(triangle[i].z > tileSpaceBbox.max.z)
88-
tileSpaceBbox.max.z = triangle[i].z;
90+
if(triangle[i].z < tileSpaceBbox.min.z) tileSpaceBbox.min.z = triangle[i].z;
91+
if(triangle[i].z > tileSpaceBbox.max.z) tileSpaceBbox.max.z = triangle[i].z;
8992
}
9093
}
9194

@@ -120,6 +123,11 @@ bool TileMaker::dumpTile(int tx, int ty, int zoom, const char* filename, MeshWri
120123
tileMesh.from_triangles(std::move(trianglesInTile));
121124
tileMesh.generate_decomposed();
122125

126+
if(normals_enabled())
127+
{
128+
tileMesh.compute_vertex_normals();
129+
}
130+
123131
return mesh_writer.write_mesh_to_file(filename, tileMesh, tileSpaceBbox);
124132
}
125133

src/cmd.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ static int subcommand_dem2tintiles(bool need_help,
5555
("max-error", po::value<double>(), "max error parameter when using terra or zemlya method")
5656
("step", po::value<int>()->default_value(1), "grid spacing in pixels when using dense method")
5757
("output-format", po::value<std::string>()->default_value("terrain"), "output tiles in terrain (quantized mesh) or obj")
58+
("normals", po::bool_switch()->default_value(false), "generate normals for terrain")
5859
#if defined(TNTN_USE_ADDONS) && TNTN_USE_ADDONS
5960
("method", po::value<std::string>()->default_value("terra"), "meshing algorithm. one of: terra, zemlya, curvature or dense")
6061
("threshold", po::value<double>(), "threshold when using curvature method");
@@ -176,6 +177,8 @@ static int subcommand_dem2tintiles(bool need_help,
176177
throw po::error(std::string("unknown method ") + meshing_method);
177178
}
178179

180+
bool write_normals = local_varmap["normals"].as<bool>();
181+
179182
RasterOverviews overviews(std::move(input_raster), min_zoom, max_zoom);
180183

181184
RasterOverview overview;
@@ -214,6 +217,7 @@ static int subcommand_dem2tintiles(bool need_help,
214217
if(!create_tiles_for_zoom_level(*overview.raster,
215218
partitions,
216219
zoom_level,
220+
write_normals,
217221
output_basedir,
218222
max_error,
219223
meshing_method,

src/dem2tintiles_workflow.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ std::vector<Partition> create_partitions_for_zoom_level(const RasterDouble& dem,
6666
bool create_tiles_for_zoom_level(const RasterDouble& dem,
6767
const std::vector<Partition>& partitions,
6868
int zoom,
69+
bool include_normals,
6970
const std::string& output_basedir,
7071
const double method_parameter,
7172
const std::string& meshing_method,
@@ -128,7 +129,7 @@ bool create_tiles_for_zoom_level(const RasterDouble& dem,
128129
}
129130

130131
// Cut the TIN into tiles
131-
TileMaker tm;
132+
TileMaker tm(include_normals);
132133
tm.loadMesh(std::move(mesh));
133134

134135
fs::create_directory(fs::path(output_basedir));

0 commit comments

Comments
 (0)