From a0f90496fa7cb7cf93a16aa5aacb2f82d83df58f Mon Sep 17 00:00:00 2001 From: Mickey Rose Date: Mon, 27 Aug 2018 11:48:18 +0200 Subject: [PATCH] BuildingSymbolizer: draw wall edges at corners --- .../process_building_symbolizer.hpp | 108 +++++++++++++++++- src/agg/process_building_symbolizer.cpp | 10 +- src/cairo/process_building_symbolizer.cpp | 10 +- src/grid/process_building_symbolizer.cpp | 1 + 4 files changed, 125 insertions(+), 4 deletions(-) diff --git a/include/mapnik/renderer_common/process_building_symbolizer.hpp b/include/mapnik/renderer_common/process_building_symbolizer.hpp index 2e888a20a..3133796b6 100644 --- a/include/mapnik/renderer_common/process_building_symbolizer.hpp +++ b/include/mapnik/renderer_common/process_building_symbolizer.hpp @@ -29,6 +29,10 @@ #include #include #include +#include + +// stl +#include namespace mapnik { @@ -44,6 +48,7 @@ private: renderer_common const& rencom_; double const height_; + double flat_wall_cos2_ = 0.9921875; // approx. cos²(5.0709°) bool render_back_side_ = false; path_type roof_vertices_{path_type::types::Polygon}; @@ -67,9 +72,31 @@ public: // colors are not always needed so they're not extracted here } - bool has_transparent_fill() const + bool has_transparent_roof() const { - return (fill_color.alpha_ & wall_fill_color.alpha_) != 255; + return fill_color.alpha_ < 255; + } + + bool has_transparent_walls() const + { + return wall_fill_color.alpha_ < 255; + } + + void flat_wall_tolerance(double angle) + { + if (angle > -180 && angle < 180) + { + double cosa = std::cos(util::radians(angle)); + // Preserve the sign of the cosine, so that comparisons against + // this value are consistent, including when the tolerance is + // greater than 90 degrees. Still, setting it above 45 degrees + // is not recommended. + flat_wall_cos2_ = std::abs(cosa) * cosa; + } + else + { + flat_wall_cos2_ = -1; + } } bool render_back_side() const @@ -166,6 +193,35 @@ public: private: + bool is_flat_wall(double ax, double ay, double bx, double by, + double cx, double cy) const + { + // less than `stroke-width` pixels away from the previous corner; + // use the points' Euclidean distance, not just delta-x, because + // slight overlap at east||west-facing wall corners is desirable + double ux = bx - ax; + double uy = by - ay; + double u2 = ux * ux + uy * uy; + if (u2 < stroke_width * stroke_width) + return true; + + // less than 1/16 of a pixel away from the next joint (E. distance) + // is too close for the angle to be reliable, could just be jittery + // geometry; + // so if such a short segment also starts less than `stroke-width` + // away from the previous corner (measuring only delta-x to prevent + // stroke overlap), assume zero deflection + double vx = cx - bx; + double vy = cy - by; + double v2 = vx * vx + vy * vy; + if (v2 < 1.0 / (16 * 16) && std::abs(ux) < stroke_width) + return true; + + // true if deflection angle is within flat wall tolerance + double dot = ux * vx + uy * vy; + return std::abs(dot) * dot >= u2 * v2 * flat_wall_cos2_; + } + bool is_visible(int orientation) const { return orientation > 0 || (orientation < 0 && render_back_side_); @@ -360,6 +416,54 @@ private: if (is_visible(orientation)) { paint_wall_faces(begin1, end1, begin2, end2, painter); + paint_wall_edges(begin1, end1, begin2, end2, painter); + } + } + + template + void paint_wall_edge(double x, double y, Context & painter) const + { + painter.stroke_from(x, y); + painter.stroke_to(x, y + height_); + painter.stroke(); + } + + template + void paint_wall_edges(size_t begin1, size_t end1, + size_t begin2, size_t end2, + Context & painter) const + { + if (begin1 >= end1 || flat_wall_cos2_ <= -1) + return; + + double x, y; + roof_vertices_->get_vertex(begin1, &x, &y); + x = std::floor(x) + 0.5; // snap to pixel center + + double x0 = x, y0 = y; // last vertex + double x1 = x, y1 = y; // last stroked joint + + for (size_t i = begin1; ++i < end1; ) + { + roof_vertices_->get_vertex(i, &x, &y); + x = std::floor(x) + 0.5; // snap to pixel center + if (!is_flat_wall(x1, y1, x0, y0, x, y)) + { + paint_wall_edge(x1 = x0, y1 = y0, painter); + } + x0 = x; + y0 = y; + } + for (size_t i = begin2; i < end2; ++i) + { + roof_vertices_->get_vertex(i, &x, &y); + x = std::floor(x) + 0.5; // snap to pixel center + if (!is_flat_wall(x1, y1, x0, y0, x, y)) + { + paint_wall_edge(x1 = x0, y1 = y0, painter); + } + x0 = x; + y0 = y; } } diff --git a/src/agg/process_building_symbolizer.cpp b/src/agg/process_building_symbolizer.cpp index 597071fee..1f8003236 100644 --- a/src/agg/process_building_symbolizer.cpp +++ b/src/agg/process_building_symbolizer.cpp @@ -100,7 +100,15 @@ void agg_renderer::process(building_symbolizer const& sym, ctx.stroke_gen.line_join(agg::round_join); rebus.setup_colors(sym, feature); - rebus.render_back_side(rebus.has_transparent_fill()); + if (rebus.has_transparent_walls()) + { + rebus.render_back_side(true); + } + else + { + rebus.render_back_side(rebus.has_transparent_roof()); + rebus.flat_wall_tolerance(181); + } rebus.apply(feature, prj_trans, ctx); } diff --git a/src/cairo/process_building_symbolizer.cpp b/src/cairo/process_building_symbolizer.cpp index d1e466865..e160f3cf9 100644 --- a/src/cairo/process_building_symbolizer.cpp +++ b/src/cairo/process_building_symbolizer.cpp @@ -79,7 +79,15 @@ void cairo_renderer::process(building_symbolizer const& sym, context_.set_line_join(ROUND_JOIN); rebus.setup_colors(sym, feature); - rebus.render_back_side(rebus.has_transparent_fill()); + if (rebus.has_transparent_walls()) + { + rebus.render_back_side(true); + } + else + { + rebus.render_back_side(rebus.has_transparent_roof()); + rebus.flat_wall_tolerance(181); + } rebus.apply(feature, prj_trans, ctx); } diff --git a/src/grid/process_building_symbolizer.cpp b/src/grid/process_building_symbolizer.cpp index 713234675..56065b0f6 100644 --- a/src/grid/process_building_symbolizer.cpp +++ b/src/grid/process_building_symbolizer.cpp @@ -90,6 +90,7 @@ void grid_renderer::process(building_symbolizer const& sym, ctx.set_feature(feature.id()); rebus.render_back_side(false); + rebus.flat_wall_tolerance(181); rebus.apply(feature, prj_trans, ctx); pixmap_.add_feature(feature);